进程与线程实现TCP并发服务器
多进程服务器端
cpp
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.109.125" //服务器IP地址
//定义一个信号处理函数,用于处理SIGCHLD信号
void handler(int signo)
{
//判断是哪个信号到位
if(signo == SIGCHLD)
{
//说明有子进程牺牲了
//使用非阻塞的形式循环回收子进程资源
while(waitpid(-1, NULL, WNOHANG) > 0);
}
}
int main(int argc, const char *argv[])
{
//将子进程向父进程发送的SIGCHLD信号,绑定到信号处理函数中
if(signal(SIGCHLD, handler) == SIG_ERR)
{
perror("signal error");
return -1;
}
//1、创建一个用于连接的套接字文件描述符
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfd)
{
perror("socket error");
return -1;
}
printf("socket 成功 sfd = %d\n", sfd); //3
//设置套接字属性:允许端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2、给套接字绑定ip地址和端口号
//2.1 封装地址信息结构体变量
struct sockaddr_in sin; //地址信息结构体变量
sin.sin_family = AF_INET; //通信域地址族
sin.sin_port = htons(SER_PORT); //端口号网络字节序
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址网络字节序
//2.2 绑定操作
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、将套接字启动监听
if(listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4、阻塞等等客户端的连接,如果有新客户端连接,
//则创建一个用于通信的套接字
struct sockaddr_in cin; //用于接受客户端套接字信息
socklen_t addrlen = sizeof(cin); //用于接受客户端套接字的长度
while(1) //循环处理多个客户端
{
//接收新的客户端的连接请求
int new_fd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
if(-1 == new_fd)
{
perror("accept error");
return -1;
}
printf("[%s:%d]发来连接,new_fd = %d\n", inet_ntoa(cin.sin_addr),\
ntohs(cin.sin_port) ,new_fd);
//给当前新的套接字创建一个子进程用于跟客户端通信
pid_t pid = fork();
if(pid > 0)
{
//关闭new_fd
close(new_fd);
}else if(pid == 0)
{
//关闭sfd文件描述符
close(sfd);
//5、使用新套接字跟客户端进行通信
while(1)
{
//从套接字中读取消息
char rbuf[128] = ""; //存放接受消息的容器
//int res = read(new_fd, rbuf, sizeof(rbuf)-1);
int res = recv(new_fd, rbuf, sizeof(rbuf)-1, 0);
if(res == 0)
{
printf("客户端已下线\n");
close(new_fd);
exit(EXIT_SUCCESS); //退出子进程
}
if(res == -1)
{
perror("read error");
close(new_fd);
close(sfd);
return -1;
}
//如果程序执行至此,表示数据成功读取下来,病放入rbuf中
printf("[%s:%d] : %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);
//给当前消息加个笑脸还回去
strcat(rbuf, "*_*");
//向套接字中写入消息
//write(new_fd, rbuf, strlen(rbuf));
send(new_fd, rbuf, strlen(rbuf), 0);
printf("发送成功\n");
}
}
//回收子进程资源
}
//6、关闭监听套接字
close(sfd);
return 0;
}
多线程客户端
cpp
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.109.125" //服务器IP地址
//定义一个结构体类型,用于主线程向分支线程传输多个数据
struct MsgInfo
{
int new_fd; //用于跟客户端通信的套接字文件描述符
struct sockaddr_in cin; //客户端套接字地址信息结构体
};
//定义处理客户端消息的线程体函数
void *deal_cli_msg(void *arg)
{
//将主线程传入的数据解析出来
int new_fd = ((struct MsgInfo*)arg)->new_fd;
struct sockaddr_in cin = ((struct MsgInfo*)arg)->cin;
//5、使用新套接字跟客户端进行通信
while(1)
{
//从套接字中读取消息
char rbuf[128] = ""; //存放接受消息的容器
//int res = read(new_fd, rbuf, sizeof(rbuf)-1);
int res = recv(new_fd, rbuf, sizeof(rbuf)-1, 0);
if(res == 0)
{
printf("客户端已下线\n");
close(new_fd);
break;
}
if(res == -1)
{
perror("read error");
close(new_fd);
return NULL;
}
//如果程序执行至此,表示数据成功读取下来,病放入rbuf中
printf("[%s:%d] : %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);
//给当前消息加个笑脸还回去
strcat(rbuf, "*_*");
//向套接字中写入消息
//write(new_fd, rbuf, strlen(rbuf));
send(new_fd, rbuf, strlen(rbuf), 0);
printf("发送成功\n");
}
//退出线程
pthread_exit(NULL);
}
/*****************************主线程********************/
int main(int argc, const char *argv[])
{
//1、创建一个用于连接的套接字文件描述符
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sfd)
{
perror("socket error");
return -1;
}
printf("socket 成功 sfd = %d\n", sfd); //3
//设置套接字属性:允许端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//2、给套接字绑定ip地址和端口号
//2.1 封装地址信息结构体变量
struct sockaddr_in sin; //地址信息结构体变量
sin.sin_family = AF_INET; //通信域地址族
sin.sin_port = htons(SER_PORT); //端口号网络字节序
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址网络字节序
//2.2 绑定操作
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、将套接字启动监听
if(listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4、阻塞等等客户端的连接,如果有新客户端连接,
//则创建一个用于通信的套接字
struct sockaddr_in cin; //用于接受客户端套接字信息
socklen_t addrlen = sizeof(cin); //用于接受客户端套接字的长度
while(1)
{
//阻塞等等客户端的连接
//对于accept函数而言,当执行到该函数时,即使没有客户端的连接
//也会预选一个当前最小的文件描述符给新套接字
//在阻塞过程中,即使有更小的,也不会选择,会留作下一次预选使用
int new_fd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
if(-1 == new_fd)
{
perror("accept error");
return -1;
}
printf("[%s:%d]发来连接,new_fd = %d\n", inet_ntoa(cin.sin_addr),\
ntohs(cin.sin_port) ,new_fd);
//定义一个结构体变量,存储通信的套接字文件描述符和客户端地址信息结构体
struct MsgInfo info = {new_fd, cin};
//创建一个分之现场,用于跟客户端进行通信
pthread_t tid = -1;
if(pthread_create(&tid, NULL, deal_cli_msg, &info)!=0)
{
printf("线程创建失败\n");
return -1;
}
//将线程设置成分离态
pthread_detach(tid);
}
//6、关闭监听套接字
close(sfd);
return 0;
}
