tcp_server的实现
来源:互联网 发布:证券编程工作 编辑:程序博客网 时间:2024/06/05 10:38
下面是单进程版本,具体问题在代码中标注。
#include<stdio.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<string.h>static void usage(const char *proc){ printf("%s [local_ip] [local_port]\n", proc);}int startup(const char *_ip, int _port){ int sock = socket(AF_INET, SOCK_STREAM, 0); //分配一个套接字 if (sock<0) { perror("socket"); exit(2); } struct sockaddr_in local; //定义IPv4协议地址且初始化,用来绑定给套接字 local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = inet_addr(_ip); //系统调用bind(),进行绑定,将套接字与TCP服务端地址绑定 if (bind(sock, (struct sockaddr*)&local, sizeof(local))<0) { perror("bind"); exit(3); } //监听这个套接字,即监听指定端口,第二个参数是socket可以排队的最大连接个数 if (listen(sock, 5)<0) { perror("listen"); exit(4); } return sock; }//argc:参数个数 argv[]:指针数组,指向各个参数(字符串)//./server 127.0.0.1 8080int main(int argc, char *argv[]){ if (argc != 3) { usage(argv[0]); return 1; } struct sockaddr_in client; //定义套接字地址 socklen_t len = sizeof(client); //封装startup函数,参数IP地址和端口号,返回监听套接字 int listen_sock = startup(argv[1], atoi(argv[2])); while (1) { //系统调用accept,他提取出监听套接字的等待队列中第一连接请求,创建一个新的套接字,并返回该套接字的文件描述符。新建立的套接字不在监听状态,原来所监听的套接字也不受该系统调用的影响。第二个参数是泛型的,需要强转。 int new_fd = accept(listen_sock, (struct sockaddr*)&client, &len); if (new_fd<0) { perror("accept"); continue; } printf("get a new client. %s:%d\n", inet_ntoa(client.sin_addr), \ ntohs(client.sin_port)); while (1) { char buf[1024]; ssize_t s = read(new_fd, buf, sizeof(buf) - 1); //读取新套接字的内容放到buf中 if (s>0) { buf[s] = 0; printf("client: %s\n", buf); write(new_fd, buf, strlen(buf)); //回显 } else { printf("read done..., break\n"); break; } } }}
#include<stdio.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<string.h>static void usage(const char *proc){ printf("%s [server_ip] [server_port]\n", proc);}int main(int argc, char *argv[]){ if (argc != 3) { usage(argv[0]); return 1; } int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock<0) { perror("socket"); return 2; } struct sockaddr_in remote; //定义IPv4协议地址,初始化为服务端地址 remote.sin_family = AF_INET; remote.sin_port = htons(atoi(argv[2])); remote.sin_addr.s_addr = inet_addr(argv[1]); //系统调用connect(),第一个参数是客户端socket套接字,第二个参数是服务端socket地址,系统自动分配端口,建立与TCP服务端的连接 if (connect(sock, (struct sockaddr*)&remote, sizeof(remote))<0) { perror("connect"); return 2; } while (1) { char buf[1024]; printf("please Entet# "); fflush(stdout); ssize_t s = read(0, buf, sizeof(buf) - 1); //读取标准输入(键盘)的内容到buf。 if (s>0) { buf[s - 1] = 0; write(sock, buf, strlen(buf)); //将buf内容写到sock套接字 ssize_t _s = read(sock, buf, sizeof(buf) - 1); //读取服务端的响应。 if (_s>0) { buf[_s] = 0; printf("server echo# %s\n", buf); } else { printf("server quit!!!\n"); break; } } }}
多进程版tcp_server(部分代码)
pid_t id = fork();if(id < 0) { perror("fork"); close(new_fd); }else if(id == 0) { //child close(listen_sock); //关闭监听套接字, if(fork() > 0) //子进程(1)生成子进程(2) { exit(0); //子进程(1)退出,其子进程(2)执行下面代码,这样子进程和父进程是爷孙进程,子进程成为孤儿进程,与父进程无关,各自执行,毫无关系。 } while(1) { char buf[1024]; ssize_t s = read(new_fd,buf,sizeof(buf)-1); if(s > 0) { buf[s] = 0; printf("client:%s\n",buf); write(new_fd,buf,strlen(buf)); } else { printf("read done ...\n"); break; } } close(new_fd); //服务器不退出,所以需要手动关闭文件描述符 }else { //father close(new_fd); //关闭文件描述符,父进程循环继续 }
多线程版tcp_server
#include<stdio.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<string.h>#include<pthread.h>static void usage(const char *proc){ printf("%s [local_ip] [local_port]\n",proc);}int startup(const char *_ip,int _port){ int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0){ perror("socket"); exit(2); } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(_ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){ perror("bind"); exit(3); } if(listen(sock,5)<0){ perror("listen"); exit(4); } return sock;}void* handle(void* arg){ int new_fd = (int)arg; while(1) { char buf[1024]; ssize_t s = read(new_fd,buf,sizeof(buf)-1); if(s > 0) { buf[s] = 0; printf("client:%s\n",buf); write(new_fd,buf,sizeof(buf)-1); } else { printf("read done...\n"); break; } }}int main(int argc,char *argv[]){ if(argc!=3) { usage(argv[0]); return 1; } struct sockaddr_in client; socklen_t len=sizeof(client); int listen_sock=startup(argv[1],atoi(argv[2])); while(1) { int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len); if(new_fd<0) { perror("accept"); continue; } printf("get a new client. %s:%d\n",inet_ntoa(client.sin_addr),\ ntohs(client.sin_port)); pthread_t id; pthread_create(&id,NULL,handle,(void*)new_fd); pthread_detach(id); }}
上面实现了三个版本的tcp_server,但是都存有一个问题,服务器中断后,不能马上启动。显示接口被占用,如下图:
原因是:虽然服务端应用程序终止了,但是TCP协议层的连接并没有完全断开,因此不能监听同样的server端口。client终止时,自动关闭socket描述符,TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL的时间后才能回到CLOSED状态,因为我们先终止了server,所以在TIME_WAIT期间不能再次监听同样的server端口。显然这是不合理的,这里完全断开是指conn_fd没有完全断开,而我们重新监听的是listen_fd,虽然占用同一个端口,但是IP地址不同,conn_fd 对应客户端的IP地址,listen_fd对应服务端地址。
解决方法:使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP不同的多个socket描述符。所以只需在server代码中的socket()和bind()调用之间插入下面代码:
int opt = 1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
阅读全文
1 0
- tcp_server的实现
- tcp_server的实现
- 【Linux】中TCP_server的实现
- TCP_server
- TCP_server
- TCP_server
- TCP_Server
- tcp_server
- tcp_server
- TCP_server
- tcp_server
- LINUX--TCP_server端的编写
- 【网络】tcp_server
- 编写tcp_server
- linux网络编程/tcp_server中server bind失败原因的调研
- tcp_server.c服务模型
- tcp_server和tcp_client
- boost完全开发指南第12章-并发编程 1 (采用boost-asio库封装的tcp_server)
- H5APP开发(一):简单计算器(使用Hbuild)
- react与vue
- 记录ubuntu上安装git后,ssh连接不上
- 关于C#上传文件出现UnauthorizedAccessException解决办法
- 前后台$.post交互并返回JSON对象
- tcp_server的实现
- python学习日记:图像模糊
- git push的时候每次都要输入用户名和密码的问题解决
- LeetCode17——Letter Combinations of a Phone Number(手机几个按键对应的字母(符号)组合)
- warning D9025: 正在重写“/D_DEBUG”(用“/U_DEBUG”)问题
- 【swift】TablviewController reloadData 的问题
- 查看虚拟机里的Centos7的IP
- Java基础教程33-ArrayList
- 【java知识点异常】e.getMessage()与e.printStackTrace()的区别