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

编写客户/服务器

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.多线程服务器的编写

代码:

更多关于云服务器域名注册虚拟主机的问题,请访问西部数码官网:www.west.cn

赞(0)
声明:本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-62778877-8306;邮箱:fanjiao@west.cn。本站原创内容未经允许不得转载,或转载时需注明出处:西部数码知识库 » 多进程多线程服务器(tcp_server)编写

登录

找回密码

注册