TCP服务器搭建(代码实现)及SO_REUSEADDR解析
来源:互联网 发布:java调用百度地图定位 编辑:程序博客网 时间:2024/05/08 08:38
<span style="font-size:18px;"></span>
相信读者对TCP协议应该有一个清晰的概念,故而在此不再细说,但在后面还是会详细的给大家介绍,首先,先给大家说一说搭建TCP服务器用的主要函数:
首先是网络字节序和主机字节序的转换函数:
其次是
在给大家看一看监听函数
还有bind和accept函数就不再给大家介绍了
server的搭建有基于进程的和基于线程的,我在这里给大家的是基于线程的。
在这里给大家说一说,多进程服务器,反应慢 , 吃资源,多线程线程服务器,线程 有上线, CPU负载过大。各有优点和好处。
<span style="font-size:18px;">server#include<stdio.h>#include<sys/socket.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<unistd.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<pthread.h>#define _PORT_ 8080#define _BACKLOG_ 10static void guse(const char* prac){ printf("%s [ip] [port]..\n",prac);}void * thread_run(void* arg){ printf("create a new thread \n"); int fd = (int)arg; while(1){ char buf[1024]; memset(buf,'\0',sizeof(buf)); ssize_t _s = read(fd,buf,sizeof(buf)-1); if(_s >0){ buf[_s] = '\0'; printf("client : %s\n",buf); write(fd,buf,strlen(buf)); }else if(_s == 0){ printf("client close..\n"); break;}else{ printf("read error...\n"); break;} } }int main(int argc,char* argv[]){if(argc != 3){guse(argv[0]);exit(0);}int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){perror("create socket error...");return 1;}struct sockaddr_in server_socket;struct sockaddr_in client_socket;bzero(&server_socket,sizeof(server_socket));server_socket.sin_family = AF_INET;server_socket.sin_addr.s_addr =htonl(atoi(argv[1]));server_socket.sin_port = htons(atoi(argv[2]));if(bind(sock,(struct sockaddr*)&server_socket,sizeof(server_socket))< 0){perror("bind error ...");return 2;}if(listen(sock,_BACKLOG_) < 0 ){perror("listen errno ...");close(sock); return 3;}printf("wait accept\n");while(1){socklen_t addrlen = sizeof(client_socket);int client_sock = accept(sock,(struct sockaddr*)&client_socket,&addrlen);if(client_sock < 0){perror("accept error...\n");close(sock);return 4;}printf("Yougot a connection from cient's ip is %s, prot is %d\n",inet_ntoa(client_socket.sin_addr),htons(client_socket.sin_port));pthread_t id;char buf_ip[1024];memset(buf_ip,'\0',sizeof(buf_ip));inet_ntop(AF_INET,&client_socket.sin_addr,buf_ip,sizeof(buf_ip));pthread_create(&id,NULL,thread_run,(void*)client_sock);pthread_detach(id);}return 0;}</span>
客户端:
<span style="font-size:18px;">#include<stdio.h>#include<sys/socket.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<unistd.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#define _BACKLOG_ 10void guse(const char* prac){printf("%s [ip] [port]..\n",prac);}int main(int argc,char* argv[]){if(argc != 3){guse(argv[0]);exit(0);}int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){perror("create socket error...");return 1;}struct sockaddr_in server_socket;bzero(&server_socket,sizeof(server_socket));server_socket.sin_family = AF_INET;//server_socket.sin_addr.s_addr =htonl(atoi(argv[1]));inet_pton(AF_INET,argv[1],&server_socket.sin_addr);server_socket.sin_port = htons(atoi(argv[2]));int ret = connect(sock,(struct sockaddr*)&server_socket,sizeof(server_socket));if(ret < 0){ perror("connect is failed..\n"); return 2;}printf("connect success \n"); char buf[1024];memset(buf,'\0',sizeof(buf));while(1){printf("client :..\n");//fflush(stdout);fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(sock,buf,strlen(buf));if(strncasecmp(buf,"quit",4) == 0){printf("quit\n");break;}printf("please wait...");read(sock,buf,sizeof(buf));printf("server :%s\n",buf);}close(sock);return 0;}</span>
来看看结果:
但是,这个程序是有BUG的,或者是有缺陷的,当你把服务器与客户端连通,CTRL+C结束服务器,然后再起服务器,这时你会看到:
这是怎么回事呢?
请大家看下面两幅图:
服务器端首先执行 LISTEN 原语进入被动打开状态( LISTEN ),等待客户端连接;
当客户端的一个应用程序发出 CONNECT 命令后,本地的 TCP 实体为其创建一个连接记录并标记为 SYN SENT 状态,然后给服务器发送一个 SYN 报文段;
服务器收到一个 SYN 报文段,其 TCP 实体给客户端发送确认 ACK 报文段同时发送一个 SYN 信号,进入 SYN RCVD 状态;
客户端收到 SYN + ACK 报文段,其 TCP 实体给服务器端发送出三次握手的最后一个 ACK 报文段,并转换为 ESTABLISHED 状态;
服务器端收到确认的 ACK 报文段,完成了三次握手,于是也进入 ESTABLISHED 状态。
在此状态下,双方可以自由传输数据。当一个应用程序完成数据传输任务后,它需要关闭 TCP 连接。假设仍由客户端发起主动关闭连接。
· 客户端执行 CLOSE 原语,本地的 TCP 实体发送一个 FIN 报文段并等待响应的确认(进入状态 FIN WAIT 1 );
· 服务器收到一个 FIN 报文段,它确认客户端的请求发回一个 ACK 报文段,进入 CLOSE WAIT 状态;
· 客户端收到确认 ACK 报文段,就转移到 FIN WAIT 2 状态,此时连接在一个方向上就断开了;
· 服务器端应用得到通告后,也执行 CLOSE 原语关闭另一个方向的连接,其本地 TCP 实体向客户端发送一个 FIN 报文段,并进入 LAST ACK 状态,等待最后一个 ACK 确认报文段;
· 客户端收到 FIN 报文段并确认,进入 TIMED WAIT 状态,此时双方连接均已经断开,但 TCP 要等待一个 2 倍报文段最大生存时间 MSL ( Maximum Segment Lifetime ),确保该连接的所有分组全部消失,以防止出现确认丢失的情况。当定时器超时后, TCP 删除该连接记录,返回到初始状态( CLOSED )。
· 服务器收到最后一个确认 ACK 报文段,其 TCP 实体便释放该连接,并删除连接记录,返回到初始状态(CLOSED )。
MSL 是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握 手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个 ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态 时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置 SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。
SO_REUSEADDR又该怎么设置呢?先看一个函数
<span style="font-size:18px;">int opt = 1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));</span>
这时,结果就是这样的:
- TCP服务器搭建(代码实现)及SO_REUSEADDR解析
- [TCP] SO_REUSEADDR ?
- 冒泡排序(解析及代码实现)
- 选择排序(解析及代码实现)
- 快速排序(解析及代码实现)
- 归并排序(解析及代码实现)
- TCP选项之SO_REUSEADDR
- TCP代理的python实现(包括客户端/服务器/TCP代理三部分代码)
- SO_REUSEADDR SO_REUSEPORT 解析
- DHCP+服务器配置+客户端搭建+及实现
- linux-socket tcp客户端服务器编程模型及代码详解
- SO_REUSEADDR测试及应用
- 插入排序(解析及代码实现 二分优化)
- 【TCP/IP】TIME_WAIT状态及地址reuse问题,SO_REUSEADDR参数详解
- 用select实现TCP回射程序(服务器及客户端)
- 搭建git服务器及利用git hook自动布署代码
- Linux搭建SVN服务器及服务端代码自动更新
- select版tcp服务器(python实现)
- 关于java限定修饰符
- The 'Apple Developer Program License Agreement' has been updated. In order to access certain members
- AngularJS入门-数据绑定
- Mysql学习总结(11)——MySql存储过程与函数
- 小问题总结
- TCP服务器搭建(代码实现)及SO_REUSEADDR解析
- Mysql学习总结(12)——21分钟Mysql入门教程
- 简单的UIScrollView循环滑动
- 关于Java中静态属性和静态代码块的执行顺序问题
- kittle连接数据库(连接SQL server、MySQL)
- HTTP Basic Authentication认证
- git与SVN协同的工作流程
- 二分查找
- Mysql学习总结(13)——使用JDBC处理MySQL大数据