1.首先贴上源码
// 目的:使用多进程处理客户端发来的请求,将客户端发来的字符串改成大写发回客户端
// 步骤:
// 1.socket()创建监听文件描述符 lfd
// 2.bind() 将lfd与ip和port绑定
// 3.listen() 设置为被动监听模式
// 4.while(1)
// {
// cfd = accept()
// pid = fork()创建子进程
// 父进程,回收子进程
// 子进程,循环读写,循环退出时,注意:子进程不能再循环去创建子进程
// }
//
// 先不使用自定义头文件实现一下
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <arpa/inet.h>
int main()
{
// --1-- int socket(int domain, int type, int protocol);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd < 0)
{
perror("socket error");
return -1;
}
// --2-- int bind(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
struct sockaddr_in serv;
// void bzero(void *s, size_t n);
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8866);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(lfd, (struct sockaddr *)(&serv), sizeof(serv));
if(ret < 0)
{
perror("bind error");
return -1;
}
// --3-- int listen(int sockfd, int backlog);
ret = listen(lfd, 10);
if(ret < 0)
{
perror("listen error");
return -1;
}
// --4-- 进入while循环,监听连接请求,建立相应子进程
int cfd;
socklen_t len;
pid_t pid;
while(1)
{
// --接受连接--int accept(int sockfd, struct sockaddr *addr,
// socklen_t *addrlen);
struct sockaddr_in client;
len = sizeof(client);
do
{
cfd = accept(lfd, (struct sockaddr *)&client, &len);
} while (cfd < 0 && (errno == ECONNABORTED || (errno == EINTR)));
if(cfd < 0)
{
perror("accept error");
return -1;
}
// 打印连接的客户端的信息
char IP[16];
inet_ntop(AF_INET, &client.sin_addr.s_addr, IP, sizeof(IP));
printf("client ip = [%s], port = [%d]\n", IP, ntohs(client.sin_port));
// --fork子进程--
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
else if(pid > 0) // 父进程
{
//关闭连接文件描述符
close(cfd);
printf("hello\n");
//回收子进程
}
else if(pid ==0) // 子进程
{
//关闭通信文件描述符
close(lfd);
// 处理业务逻辑 读写
char buf[128];
int n = 0;
int i = 0;
while(1)
{
memset(buf, 0x00, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
if(n <= 0)
{
perror("read error or client closed");
break;
}
printf("%s\n", buf);
for(i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, sizeof(buf));
}
// 读错误退出业务逻辑
close(cfd);
exit(-1); // 也可以 break,退出外面的 while(1)循环,接着就退出了整个子进程
// exit() 更直观一点,表示到此没有什么要处理的了,直接退出整个进程吧~
}
}
return 0;
}
2.注意点:
这个案例没有使用自定义的库函数(里面对socket()、bind()、listen()等进行了封装,实现异常处理)。所以这里的异常处理是直接写在了代码里。
但是没有对读写操作时可能被信号打断的情况的异常进行处理。
3.有待改进的地方:
父进程没有对子进程的退出进行回收,会产生僵尸进程。后面会有一个回收子进程的版本(使用自定义头文件对异常进行处理(头文件下载于网络))
4.实验:
①:客户端主动连接服务端:
②:服务端接受三个客户端的连接(还可以更多),在服务端打印客户端的信息: