接上篇标题
服务器每接收到一个客户端请求,就开启一个线程,循环读取客户端发来的数据,并向客户端发送数据,当客户端close或者关闭练连接后,打印此次关闭的客户端的ip地址和端口号。
服务器端
#include <arpa/inet.h>
#include <netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<signal.h>
#include<errno.h>
#include<time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<pthread.h>
#include "msg.h"
int sockfd;
void sig_handler(int signo) {
if (signo == SIGINT){
printf("server close\n");
/*步骤6:关闭服务器端的socket*/
close(sockfd);
exit(1);
}
}
void do_service(int fd) {
/*和客户端进行读写操作(双向通信)*/
char buf[512];
while (1){
memset(buf, 0, sizeof(buf));
size_t size;
if((size = read_msg(fd, buf, sizeof(buf))) < 0) {
perror("protocal error");
break;
} else if(size == 0) { //客户端已经断开连接,读取到的size大小为0
break;
} else {
printf("%s\n", buf);
if(write_msg(fd, buf, sizeof(buf)) < 0) {
if(errno == EPIPE) { //客户端读端关闭
break;
}
perror("protocal error");
}
}
}
}
void out_fd(int fd) {
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
if(getpeername(fd, (struct sockaddr*)&addr, &len) < 0) {
perror("getpeername error");
return;
}
char ip[16];
memset(ip, 0, sizeof(ip));
int port = ntohs(addr.sin_port);
inet_ntop(AF_INET,
&addr.sin_addr.s_addr, ip, sizeof(ip));
printf("%16s(%5d) closed\n", ip, port);
}
void* th_fn(void *arg) {
int fd = *((int *)arg);
do_service(fd);
out_fd(fd);
close(fd);
return (void*)0;
}
int main(int argc, char *argv[]) {
if (argc < 2){
printf("usage:%s #port\n",argv[0]);
exit(1);
}
if (signal(SIGINT, sig_handler) == SIG_ERR){
perror("signal sigint error");
exit(1);
}
/*步骤1:创建socket套接字*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/*步骤2:调用bind将socket和地址绑定(包括ip,port)*/
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; //IPV4
serveraddr.sin_port = htons(atoi(argv[1]));
serveraddr.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
perror("bind error");
exit(1);
}
/*步骤3:调用listen函数启动监听(指定port监听)*/
if(listen(sockfd,10) < 0) {
perror("listen error");
exit(1);
}
/*步骤4:调用accept函数从队列中获得一个客户端的请求连接
注意:若没有客户端连接,调用accept函数将阻塞,直到货到衣蛾客户端连接
*/
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
// 设置线程属性分离
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
while (1){
// 主控线程负责调用accept去获取客户端的连接
int fd = accept(sockfd, NULL, NULL);
if(fd < 0) {
perror("accept error");
continue;
}
/**
* 步骤5:启动子线程调用IO函数(read/write)和连接的客户端进行双向的通信
*/
pthread_t th;
int err;
if((err = pthread_create(&th, &attr, th_fn, (void *)&fd)) != 0) {
perror("pthread create error");
}
pthread_attr_destroy(&attr);
}
return 0;
}
编译服务器端:
gcc -o bin/echo_server_th -Iinclude obj/msg.o src/echo_server_mutil_th.c -lpthread
*测试
- 运行服务器
./bin/echo_server_th 8888 - 运行多个客户端
./bin/echo_client 127.0.0.1 8888 - 多个客户端同时向服务器发送数据,关闭其中一个客户端,不影响其他客户端,同时服务器端会打印此次关闭的客户端的ip和port。
网友评论