PPC的C/C++和人工智能学习笔记
每一篇学习笔记,都只是为了更好地掌握和理解

linux开发(10)_网络编程(5)简易tcp服务端

前面做了个简易的tcp测试用客户端,接下来就要使用简易的、select、poll、epoll等端口复用方法来分别实现服务端,顺便比较下性能和区别。

 

在测试客户端中,有2种模式,一种是短连接(收发1次就关闭,端口4444),一种是长连接(连续收发100次关闭,端口5555)。

 

在简易服务端的实现中,4444端口的短连接使用1个线程来完成accept、recv、send、close;而5555端口的accept用1个线程实现,有连接进来的时候开启新的线程来完成recv和send(没有使用线程池的方式,由于控制了客户端长连接线程只有10个,所以也不要担心服务端会有很多线程同时工作,后面会改为线程池的方式)。

 

注意:简易服务端的socket工作在阻塞模式,为了减少死锁问题,在收发的时候设置了timeout属性。

 

补充几个知识点:

setsockopt中参数SO_REUSEADDR的用法和含义:

通常情况下,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。

SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项,否则,每次重启服务必须要等2分钟才可以bind。

TCP,先调用close()的一方会进入TIME_WAIT状态。

  • SO_REUSEADDR允许启动一个监听服务器并捆绑其端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
  • SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
  • SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
  • SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

 

int on=1;

setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));

 

//easy tcp server

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

void* print_thread(void* arg); //打印线程函数
void* short_srv(void* arg); //短连接线程函数
void* long_srv_accept(void* arg); //长连接accept线程函数
void* long_srv_recv_send(void* arg); //长连接recv send线程函数
int init_server_socket(int port); //初始化服务端socket,创建、绑定、监听
void set_block_timeout(int fd,int ms); //设置阻塞模式的timeout
int data_send(int fd,void* buf,int len); //发送数据
int data_recv(int fd,void* buf,int len); //接收数据

#define L_SEND_RECV_COUNT 100 //长连接收发次数
int g_short_now = 0; //当前已经完成的 短连接数
int g_long_now = 0; //当前已经完成的 长连接数
volatile int g_error = 0; //错误连接数

int main(){
 pthread_t thread_print,thread_id;
 int err;
 err = pthread_create(&thread_print,NULL,print_thread,NULL);
 if (err != 0){
 perror("pthread_create print_thread error!");
 exit(EXIT_FAILURE);
 }
 //创建短连接服务线程(1个线程处理所有短连接)
 if(pthread_create(&thread_id,NULL,short_srv,NULL)!=0){
 perror("pthread_create short_srv error!");
 exit(EXIT_FAILURE); 
 }
 //创建长连接accept线程(1个线程处理所有长连接的accept)
 if( pthread_create(&thread_id,NULL,long_srv_accept,NULL)!=0 ){
 perror("pthread_create long_srv_accept error!");
 exit(EXIT_FAILURE);
 }
 
 pthread_join(thread_print,NULL); //等待打印线程结束
 return 0;
}

void* print_thread(void* arg){
 int i = 0;
 printf("sec\t\ts_all\t\tl_all\t\ttotal_error\n");
 while(1){
 sleep(1);
 printf("%d\t\t%d\t\t%d\t\t%d\n",++i,g_short_now,g_long_now,g_error);
 }
}

void* short_srv(void* arg){
 pthread_detach(pthread_self());
 int listen_sock = init_server_socket(4444);
 if(listen(listen_sock,SOMAXCONN) < 0 ){
 perror("listen error!");
 exit(EXIT_FAILURE);
 }
 struct sockaddr_in remote_addr;
 int len = sizeof(remote_addr);
 char buf[1024];
 while(1){
 int new_sock;
 new_sock = accept(listen_sock,(struct sockaddr*)&remote_addr,&len);
 if(new_sock<0){
 perror("accept error!");
 exit(EXIT_FAILURE);
 }
 ++g_short_now;
 //设置new_sock timeout 3000ms
 set_block_timeout(new_sock,3000);
 if(data_recv(new_sock,buf,sizeof(buf)) == 0)
 data_send(new_sock,buf,sizeof(buf));
 close(new_sock);
 }
}

void* long_srv_accept(void* arg){
 pthread_detach(pthread_self());
 int listen_sock = init_server_socket(5555);
 if(listen(listen_sock,SOMAXCONN) < 0){
 perror("listen error!");
 exit(EXIT_FAILURE);
 }
 struct sockaddr_in remote_addr;
 int len = sizeof(remote_addr);
 int i=0;
 int fd_arr[20]; //临时存放 new_sock的数组,用于传递参数给线程
 while(1){
 int new_sock;
 new_sock = accept(listen_sock,(struct sockaddr*)&remote_addr,&len);
 if(new_sock<0){
 perror("accept error!");
 exit(EXIT_FAILURE);
 }
 fd_arr[i] = new_sock;
 ++g_long_now;
 //设置new_sock timeout 3000ms
 set_block_timeout(new_sock,3000);
 //创建线程,为长连接收发数据
 pthread_t thread_id;
 //注意:下面的参数传递,直接用(void*)&new_sock有可能会出错!
 if( pthread_create(&thread_id,NULL,long_srv_recv_send,(void*)&fd_arr[i]) !=0 ){
 perror("pthread_create long_srv_recv_send error!");
 exit(EXIT_FAILURE);
 }
 i = (i+1) % 20;
 }
}

void* long_srv_recv_send(void * arg){
 int fd = *(int*)arg;
 pthread_detach(pthread_self());
 int i=0;
 char buf[1024*40];
 for(i=0;i<L_SEND_RECV_COUNT;++i){
 if(data_recv( fd, buf, sizeof(buf)) == 0)
 data_send( fd, buf, sizeof(buf));
 }
 close(fd);
}

int init_server_socket(int port){
 int fd = socket(AF_INET,SOCK_STREAM,0);
 assert(fd>=0);
 struct sockaddr_in server_addr;
 server_addr.sin_family = AF_INET;
 server_addr.sin_port = htons(port);
 server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 bzero(&server_addr.sin_zero,8);
 int on=1;
 if( setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on)) < 0 ){
 perror("setsockopt error!");
 exit(EXIT_FAILURE);
 }
 if( bind(fd,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0 ){
 perror("bind error!");
 exit(EXIT_FAILURE);
 }
 return fd;
}

void set_block_timeout(int fd,int ms){
 struct timeval timeout;
 timeout.tv_sec = ms / 1000;
 timeout.tv_usec = ( ms % 1000 ) * 1000;
 if( setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout)) < 0 ){
 perror("setsockopt error!");
 exit(EXIT_FAILURE);
 }
 if( setsockopt(fd,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout)) < 0 ){
 perror("setsockopt error!");
 exit(EXIT_FAILURE);
 }
}

int data_send(int fd,void* buf,int len){
 int ret=-1;
 int index = 0;
 int nbytes = 0;
 while(1){
 nbytes = send(fd,buf+index,len-index,0);
 if(nbytes <=0){
 perror("send error!");
 ret = -1;
 break;
 }
 index += nbytes;
 if(index == len){
 ret = 0;
 break;
 }
 }
 if(ret == -1)
 __sync_fetch_and_add(&g_error,1);
 return ret;
}

int data_recv(int fd,void* buf,int len){
 int ret = -1;
 int index = 0;
 int nbytes = 0;
 while(1){
 nbytes = recv(fd,buf+index,len-index,0);
 if(nbytes <=0){
 perror("recv error!");
 ret = -1;
 break;
 }
 index += nbytes;
 if(index == len){
 ret = 0;
 break;
 }
 }
 if(ret == -1)
 __sync_fetch_and_add(&g_error,1);
 return ret;
}


 

用前面的客户端测试结果如下:(全部短连接100万次,每次收发1K数据,全部长连接1万次,每次收发100次40K数据)

 

(2017-11-23 www.vsppc.com)

学习笔记未经允许不得转载:PPC的C/C++和人工智能学习笔记 » linux开发(10)_网络编程(5)简易tcp服务端

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址