多进程多线程服务器(tcp_server)编写

  • 时间:
  • 来源:csdn

   

编写客户/服务器

1.编写单进程客户/服务器(Version1)

代码清单:
tcp_servet

#include<stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<stdlib.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> static void usage(const char* proc) { printf("Usage: %s [local_ip] [local_port]",proc);//提示输入出错,应该输入./tcp_servet ip地址 端口号(运行服务器) } int startup(const char* ip,int port)//创建套接字 { int sock = socket(AF_INET,SOCK_STREAM,0);//socket()创建套接字函数 if(sock<0) { perror("socket");//提示创建套接字失败 exit(2); } //socket的返回值只是一个文件描述符,为了进行网络通信,我们还需将其与服务器 ip地址和端口号进行绑定 struct sockaddr_in local;//结构体struct sockaddr_in是struct sockaddr的具体化 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){//bind(绑定函数 perror("bind"); exit(3); } //监听 if(listen(sock,10)<0)//服务器24小时监听,等待连接请求 { perror("listen"); exit(4); } return sock; } //Version 1 //./tcp_servet 127.0.0.1 8080 本地环回 int main(int argc,char* argv[]) { if(argc!=3){ usage(argv[0]); return 1; } int listen_sock = startup(argv[1],atoi(argv[2]));//create socket int new_sock; while(1){ struct sockaddr_in client; socklen_t len = sizeof(client); new_sock = accept(listen_sock,(struct sockaddr*)&client ,&len);//accept创建新的socket,用来处理客户段请求,listen_sock只负责监听 if(new_sock<0) { perror("accept"); exit(5); continue; } //成功获取到客户端,打印客户端的 ip和端口号,accept的第二个参数是输出型参数,由它可得客户端的ip和端口号 printf("get an new client: %s, %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); break; } //客户端和服务器端的数据传输 char buf[1024]; while(1){ int r = read(new_sock,buf,sizeof(buf)-1);//从网络中读取数据(客户端发送数据)放入buf中; if(r<0){//读取失败 perror("read"); close(new_sock); break; }else if(r==0){//客户端已关闭 close(new_sock); printf("client qiuit...\n"); break; } //读取成功 buf[r] = 0; printf("clinet: %s\n",buf); write(new_sock,buf,strlen(buf));//回送数据 } return 0; }

tcp_client.c客户端

#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<arpa/inet.h> #include<netinet/in.h> #include<string.h> void static Usage(const char* proc)//提示打印格式 { printf("Usage: %s [local_ip] [local_port]\n",proc);//运行客户端./tcp_servet 服务器ip 服务器端口号 } //./tcp_clinet servet_ip servet_port int main(int argc,char* argv[]) { if(argc!=3){ Usage(argv[1]); exit(1); } int sock = socket(AF_INET,SOCK_STREAM,0);//创建套接字 if(sock < 0 ){ perror("socket"); exit(2); } //客户端socket不绑定 struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[2]));//htons将本地端口号转为网络端口号 server.sin_addr.s_addr = inet_addr(argv[1]); //给数据结构成员赋值 //connect()链接服务器 if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){ perror("connect"); exit(3); } //客户端发送数据 char buf[1024]; while(1){ printf("Please Enter$"); fflush(stdout); ssize_t s = read(0,buf,sizeof(buf)-1);//从标准输入读数据到buf if(s>0){//读取成功 buf[s-1] = 0; write(sock,buf,strlen(buf));//将数据写到网络中 s = read(sock,buf,sizeof(buf)-1);//读取回显数据 if(s>0){ buf[s] = 0; printf("servetr echo#:%s\n",buf); } } return 0; }

注意要点

上述的服务器地址采用127.0.0.1,故该服务器没有通过网络通信,还是一直监听所在主机上的客户链接请求,这是本地环回;

该客户/服务器是一个单进程的服务器,一次只能链接一个客户请求,因为我们在收到一个客户请求后,执行完客户端的请求才接受下一个请求的链接

客户端的套接字可以绑定也可以不绑定,但一般不进行绑定,因为一点绑定服务器的ip,那么若别的客户端想链接,链接不上

涉及函数以及命令

创建socket
这里写图片描述

domain表示域,ipv4为AF_INET;
type表示类型,SOCK_STREAM(面向字节流)
protocal表示协议,单个通信协议情况下为0

bind绑定
这里写图片描述

sockfd : socket()的返回值,一个文件描述符
addr:一个struct sockaddr_in 的结构体
这里写图片描述
struct in_addr结构体成员是s_addr,表示ip地址;
addrlen:结构体大小

accept创建新的socket
这里写图片描述
addr是一个输出型参数,输出的是客户端的struct sockaddr_in 的内容
addrlen是一个输入输出型参数 ip转换
这里写图片描述 本地端口号与网络端口号的互换
这里写图片描述 监听
这里写图片描述
backlog : 是操作系统底层连接队列,不能设置过大; connect函数(客户端连接服务器)
这里写图片描述

2.多进程服务器编写

代码清单:
tcp_servet.c

#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<string.h> //Version2 void static usage(const char* proc) { printf("Usage: %s [local_ip] [local_port]\n",proc); } int startup(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,10)<0){ perror("listen"); exit(4); } return sock; } //./tcp_servet local_ip local_port int main(int argc,char* argv[]){ if(argc!=3){ usage(argv[0]); return 1; } int listen_sock = startup(argv[1],atoi(argv[2])); int new_sock; while(1){ struct sockaddr_in client; socklen_t len = sizeof(client); new_sock = accept(listen_sock,(struct sockaddr*) &client,&len); if(new_sock < 0){ perror("accept"); exit(5); continue; } printf("get an new client:%s,%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); break; } //创建了一个子进程代替我们处理客户端的请求---与上述单进程的不同之处 pid_t id = fork(); if(id<0){ perror("fork"); exit(6); }else if(id==0){//child close(listen_sock);//关闭不必要的文件 if(fork > 0){//两次fork exit(7); } char buf[1024]; while(1){ int r= read(new_sock); if(r<0){ perror("read"); close(new_sock); break; }else if(r==0){ close(new_sock); printf("clinet quit...\n"); break; } buf[r] = 0; printf("clien: %S\n",buf); write(new_sock,buf,strlen(buf)); close(new_sock); } }else{//father close(new_sock);//父进程继续负责监听客户请求,必须关闭new_sock文件 } }

注意要点

与单进程的唯一不同的是, fork了一个子进程,每次客户端的请求交给子进程完成,父进程只负责监听 ; 两次 fork,为了能够让父进程只负责监听,而不阻塞式的等待子进程的退出,我们采用两次fork,此时存在两个进程,两个进程存在爷孙关系,“孙子”进程此时由于父进程已经退出,所以它是孤儿进程,由 init进程回收,与此刻的父进程没有关系

pid_t id = fork(); if(id<0){ perror("fork"); exit(6); }else if(id==0){//child close(listen_sock);//关闭不必要的文件 if(fork > 0){//两次fork,第二次创建完子进程后该父进程退出 exit(7); } 父进程必须关闭new_sock文件,因为子进程拷贝了父进程的文件描述符表,两者表是一样的,若不关闭,每次客户端发送来一个新的连接,就要在父进程的文件描述符表中打开一个文件,这样浪费资源,而且很快会用完;(子进程处理完信息后退出时关闭了文件描述符表,而父进程一直处于运行状态,所以必须主动关闭)

3.多线程服务器的编写

代码:
这里写图片描述

这里写图片描述