一、TCP服务器
1、socket()
参数:
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
protocol参数的介绍从略,指定为0即可。
返回值:
socket()打开一个网络通讯端口,
如果成功的话,就像open()一样返回一个文件描述符;
应用程序可以像读写文件一样用read/write在网络上收发数据;
如果socket()调用出错则返回-1
2、bind()
功能:
bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端⼝口号。
参数:
上一篇文章讲过,struct sockaddr *是一个通用指针类型,
myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
返回值:
bind()成功返回0,失败返回-1。
3、listen():实现监听
参数:
sockfd:listen()声明sockfd处于监听状态,
backlog:最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大
返回值:成功返回0,失败返回-1
4、accept():获得连接并建立连接
参数:
addr是一个传出参数,accept()返回时传出客户端的地址和端口号。传NULL,表示不关心客户端的地址;
addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)
如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来
5、简单实现TCP网络程序服务器:
利用以上函数:
1 #include<stdio.h> 2 #include<errno.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<sys/types.h> 6 #include<sys/socket.h> 7 #include<netinet/in.h> 8 9 #define _PORT_ 8080 10 #define _BACKLOG_ 10 11 12 int main(){ 13 int sock=socket(AF_INET,SOCK_STREAM,0); 14 if(sock<0){ 15 printf("create socket error,error is : %d,errstring is : %s\n",errno,strerror(errno)); 16 } 17 18 struct sockaddr_in server_socket; 19 struct sockaddr_in client_socket; 20 bzero(&server_socket,sizeof(server_socket)); 21 server_socket.sin_family=AF_INET; 22 server_socket.sin_addr.s_addr=htonl(INADDR_ANY); 23 server_socket.sin_port=htons(_PORT_); 24 25 if(bind(sock,(struct sockaddr*)&server_socket,sizeof(struct sockaddr_in))<0){ 26 printf("bind error,error code is : %d,error string is : %s\n",errno,strerror(errno)); 27 close(sock); 28 return 1; 29 } 30 if(listen(sock,_BACKLOG_)<0){ 31 printf("listen error,error code is : %d,error string is : %s\n",errno,strerror(errno)); 32 close(sock); 33 return 2; 34 } 35 printf("bind and listen success,wait accept...\n"); 36 for(;;){ 37 socklen_t len=0; 38 int client_sock=accept(sock,(struct sockaddr*)&client_socket,&len); 39 if(client_sock<0){ 40 printf("accept error,errno is : %d,errstring is:%s\n",errno,strerror(errno)); 41 close(sock); 42 return 3; 43 } 44 char buf_ip[INET_ADDRSTRLEN]; 45 memset(buf_ip,'\0',sizeof(buf_ip)); 46 inet_ntop(AF_INET,&client_socket.sin_addr,buf_ip,sizeof(buf_ip)); 47 48 printf("get connect,ip is : %s port is : %d\n",buf_ip,ntohs(client_socket.sin_port)); 49 while(1){ 50 char buf[1024]; 51 memset(buf,'\0',sizeof(buf)); 52 53 read(client_sock,buf,sizeof(buf)); 54 printf("client :#%s\n",buf); 55 56 printf("server :$ "); 57 58 memset(buf,'\0',sizeof(buf)); 59 fgets(buf,sizeof(buf),stdin); 60 buf[strlen(buf)-1]='\0'; 61 write(client_sock,buf,strlen(buf)+1); 62 printf("please wait...\n"); 63 } 64 } 65 close(sock); 66 return 0; 67 } 68
二、TCP客户端
1、connect():客户端建立连接
connect()连接服务器:
参数:
connect和bind的参数形一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址
返回值:
成功返回0,出错返回-1
2、关于bind()
客户端没有必要调用bind()固定一个端口号,否则如果在同一台机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接;
服务器也不是必须调用bind(), 但如果服务器不调用bind(), 内核会自动给服务器分配监听端口, 每次启动服务器时端口号都不一样, 客户端要连接服务器就会遇到麻烦。
3、简单实现TCP网络程序客户端:
#include<stdio.h> #include<errno.h> #include<unistd.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #define SERVER_PORT 8080 #define SERVER_IP "127.0.0.1" int main(int argc,char *argv[]){ if(argc!=2){ printf("Usage : client IP\n"); return 1; } char *str=argv[1]; char buf[1024]; memset(buf,'\0',sizeof(buf)); struct sockaddr_in server_sock; int sock=socket(AF_INET,SOCK_STREAM,0); bzero(&server_sock,sizeof(server_sock)); server_sock.sin_family=AF_INET; //inet_pton(AF_INET,SERVER_IP,&server_sock.sin_addr); server_sock.sin_port=htons(SERVER_PORT); server_sock.sin_addr.s_addr=inet_addr(SERVER_IP); int ret=connect(sock,(struct sockaddr *)&server_sock,sizeof(server_sock)); if(ret<0){ printf("connect failed...,errno is : %d,errstring is : %s\n",errno,strerror(errno)); return 1; } printf("connect success...\n"); while(1){ printf("client : # "); fgets(buf,sizeof(buf),stdin); buf[strlen(buf)-1]='\0'; write(sock,buf,sizeof(buf)); if(strncasecmp(buf,"quit",4)==0){ printf("quit!\n"); break; } printf("please wait...\n"); read(sock,buf,sizeof(buf)); printf("server :$ %s\n",buf); } close(sock); return 0; }
测试:
运行服务器:
我们用netstat查看一下监听状态:
可以看到tcp_server程序监听8080端口。
运行客户端,进程通信成功。
三、实现简单的TCP多进程网络程序
1、实现多进程TCP,我们就要调用系统函数fork来创建进程。
通过每个请求, 创建子进程的方式来支持多连接。我们可以让父进程监听客户端的请求连接,子进程对已经建立连接的客户端提供服务。
服务器端代码如下:
#include<stdlib.h> #include<stdio.h> #include<errno.h> #include<unistd.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/wait.h> #include<netinet/in.h> void Usage(){ printf("usage : ./server [ip][port]\n"); } void ProcessRequest(int client_fd,struct sockaddr_in* client_addr){ char buf[1024]={0}; for(;;){ ssize_t read_size=read(client_fd,buf,sizeof(buf)); if(read_size<0){ perror("read"); continue; } if(read_size==0){ printf("client: %s say bye!\n",inet_ntoa(client_addr->sin_addr)); close(client_fd); break; } buf[read_size]='\0'; printf("client %s say: %s\n",inet_ntoa(client_addr->sin_addr),buf); write(client_fd,buf,strlen(buf)); } } void CreateWorker(int client_fd,struct sockaddr_in* client_addr){ pid_t pid=fork(); if(pid<0){ perror("fork"); return; }else if(pid==0){ //child if(fork()==0){ //grand_child ProcessRequest(client_fd,client_addr); } exit(0); } else{ //father close(client_fd); waitpid(pid,NULL,0); } } int main(int argc,char* argv[]){ if(argc!=3){ Usage(); return 1; } struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr(argv[1]); addr.sin_port=htons(atoi(argv[2])); int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0){ perror("socket"); return 1; } int ret=bind(fd,(struct sockaddr*)&addr,sizeof(addr)); if(ret<0){ perror("bind"); return 1; } ret=listen(fd,10); if(ret<0){ perror("listen"); return 1; } for(;;){ struct sockaddr_in client_addr; socklen_t len=sizeof(client_addr); int client_fd=accept(fd,(struct sockaddr*)&client_addr,&len); if(client_fd<0){ perror("accept"); continue; } char bufip[32]; bufip[0]=0; inet_ntop(AF_INET,&client_addr.sin_addr,bufip,sizeof(bufip)); printf("get connect,ip is:%s port is:%d\n",bufip,ntohs(client_addr.sin_port)); CreateWorker(client_fd,&client_addr); } close(fd); return 0; }
2、优缺点
缺点:
(1)只有链接的请求到达,才会创建进程,影响性能。可以利用进程池提前创建好。
(2)多进程服务器同时服务的人数有上限,且十分占用资源
(3)进程增多会导致切换成本太大,进而影响性能。
优点:
(1)可以处理多用户
(2)编写简单
(3)稳定。若一进程挂掉,不会影响其他进程。
四、实现简单的TCP多线程网络程序
1、通过每个请求,创建一个线程的方式来支持多连接:
要实现多线程的TCP,我们必须调用pthread_create函数创建新线程。多线程TCP需要创建线程就可以了,与多进程主逻辑不变。
服务器端代码如下:
#include<stdlib.h> #include<stdio.h> #include<errno.h> #include<unistd.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/wait.h> #include<netinet/in.h> #include<pthread.h> void Usage(){ printf("usage : ./server [ip][port]\n"); } void ProcessRequest(int client_fd,struct sockaddr_in* client_addr){ char buf[1024]={0}; for(;;){ ssize_t read_size=read(client_fd,buf,sizeof(buf)); if(read_size<0){ perror("read"); continue; } if(read_size==0){ printf("client: %s say bye!\n",inet_ntoa(client_addr->sin_addr)); close(client_fd); break; } buf[read_size]='\0'; printf("client %s say: %s\n",inet_ntoa(client_addr->sin_addr),buf); write(client_fd,buf,strlen(buf)); } } typedef struct ARG{ int fd; struct sockaddr_in addr; }Arg; void* CreateWorker(void *ptr){ Arg* arg=(Arg*)ptr; ProcessRequest(arg->fd,&arg->addr); free(arg); return NULL; } int main(int argc,char* argv[]){ if(argc!=3){ Usage(); return 1; } struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr(argv[1]); addr.sin_port=htons(atoi(argv[2])); int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0){ perror("socket"); return 1; } int ret=bind(fd,(struct sockaddr*)&addr,sizeof(addr)); printf("qqq"); if(ret<0){ perror("bind"); return 1; } printf("qqq"); ret=listen(fd,10); if(ret<0){ perror("listen"); return 1; } for(;;){ struct sockaddr_in client_addr; socklen_t len=sizeof(client_addr); int client_fd=accept(fd,(struct sockaddr*)&client_addr,&len); if(client_fd<0){ perror("accept"); continue; } char bufip[32]; bufip[0]=0; inet_ntop(AF_INET,&client_addr.sin_addr,bufip,sizeof(bufip)); printf("get connect,ip is : %s port is :%d\n",bufip,ntohs(client_addr.sin_port)); pthread_t tid=0; Arg* arg=(Arg*)malloc(sizeof(Arg)); arg->fd=client_fd; arg->addr=client_addr; pthread_create(&tid,NULL,CreateWorker,(void*)arg); pthread_detach(tid); } close(fd); return 0; }
2、优缺点
与多进程比较,多线程只是其的一个缓解,在性能、占用资源与切换成本等方面,都有了一定的提高。
但是多了一个缺点,不稳定,若有一个线程出现异常,则所有的线程挂掉。
注意:
在创建子进程后,子进程必须回收,否则就会造成内存泄漏。
新线程在退出后我们也必须回收,否则会造成资源浪费。