在Linux中通过流式套接字编程(TCP),实现一个并发服务器的访问回显,适合刚学完Linux套接字编程的朋友进行巩固训练
具体功能:
recv()
函数,当返回值为0的时候,表示客户端已经关闭套接字,即客户端退出。recv()
收到服务器关闭的信息,然后客户端主动退出#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_ADDR "172.17.44.154"
#define BUFSIZE 100
void sigchld_handler(int arg);
int main(int argc, const char *argv[])
{
int socket_fd, new_fd;
struct sockaddr_in server_addr, cli_addr;
char buf[BUFSIZE];
int pid;
struct sigaction sig;
/* 注册中断信号处理函数 */
sig.sa_handler = sigchld_handler;
sigaction(SIGCHLD, &sig, NULL);
/* 创建套接字,并获取套接字描述符 */
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == socket_fd) {
perror("socket");
exit(-1);
}
/* 绑定地址 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5001);
inet_pton(AF_INET, SERVER_ADDR, (void*)&server_addr.sin_addr.s_addr); //地址转换
if (-1 == bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
perror("bind");
exit(-1);
}
/* 转换为被动连接套接字 */
if (-1 == listen(socket_fd, 5)) {
perror("listen");
exit(-1);
}
#if 0 //单进程服务器
/* 获取已连接套接字 */
socklen_t len = 0;
new_fd = accept(socket_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len);
if (-1 == new_fd) {
perror("accept");
exit(-1);
}
printf("accept socket!\nclient ip :%s port:%d\n", inet_ntoa(cli_addr.sin_addr), cli_addr.sin_port);
while (1) {
memset(buf, 0, BUFSIZE);
if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //接受数据
printf("the client is closed\n");
break;
}
printf("read:%s\n", buf);
send(new_fd, buf, BUFSIZE, 0); //回应客户端
}
close(new_fd);
#else //多进程并发服务器
while (1) {
socklen_t len = 0;
new_fd = accept(socket_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len);
if (-1 == new_fd) {
if (errno == EINTR) continue; //accept可能会被信号中断
perror("accept");
exit(-1);
}
/* 并发服务器:子进程中进行TCP通信 */
pid = fork();
if (pid == -1) {
perror("fork");
exit(0);
} else if (pid == 0) {
close(socket_fd); //关闭监听套接字
while (1) {
memset(buf, 0, BUFSIZE);
if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //接受数据,只有当客户端主动关闭的时候,才退出线程,还要对关闭之后的子进程
printf("the client socket %s is closed\n", inet_ntoa((struct in_addr)cli_addr.sin_addr));
close(new_fd); //退出之前记得关闭 已连接套接字
exit(0); //通过信号处理函数进行回收
}
getsockname(new_fd, (struct sockaddr*)&cli_addr, (socklen_t *)&len); //获取连接套接字信息
printf("recv client IP:%s data:%s\n", inet_ntoa((struct in_addr)cli_addr.sin_addr), buf);
send(new_fd, buf, BUFSIZE, 0); //回应客户端
}
} else {
close(new_fd); //父进程关闭 已连接套接字
}
}
#endif
close(socket_fd);
return 0;
}
void sigchld_handler(int arg)
{
int child_pid;
if (SIGCHLD == arg) {
if ((child_pid = waitpid(-1, NULL, WNOHANG)) == -1) {
perror("sigchld");
}
printf("a client %d is end\n", child_pid);
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_ADDR "172.17.44.154"
#define BUFSIZE 100
int main(int argc, const char *argv[])
{
int new_fd;
struct sockaddr_in server_addr;
char buf[BUFSIZE];
/* 创建套接字,并获取套接字描述符 */
new_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == new_fd) {
perror("socket");
exit(-1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5001);
inet_pton(AF_INET, SERVER_ADDR, (void*)&server_addr.sin_addr.s_addr);
if (-1 == connect(new_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
perror("connect");
exit(-1);
}
while (1) {
printf("input:");
fgets(buf, BUFSIZE, stdin); //获取数据
if (-1 == send(new_fd, buf, BUFSIZE, 0)) { //发送数据
perror("send");
close(new_fd);
exit(-1);
}
if (0 == recv(new_fd, buf, BUFSIZE, 0)) { //收到数据
printf("server closed\n");
break;
}
printf("recv:%s\n", buf);
}
close(new_fd);
return 0;
}
PS:这里是在同一主机下做实验的,所以各个客户端的IP地址都是一样的
正常运行的状态如下:
当有一个客户端退出时,服务器会显示信息,但是对其他客户端的服务正常进行:
当服务器主动关闭之后,所有客户端都会收到服务器关闭的信息,并且主动退出: