拟实现一个基本的流式套接字客户端/服务器通信程序。在该程序中,客户端和服务器将按照如下步骤交互:
/*************************************************************************
> File Name: timeserver.c
> Author:
> Mail:
> Created Time: Tue 10 Apr 2018 09:32:39 AM CST
************************************************************************/
/*
* 服务器提供的服务:
* 客户端向服务器发出日期时间请求字符串,格式:%D %Y %A %T等
* 服务器从网络接收到日期时间请求字符串后,根据字符串格式生成对应的日期时间值返回给客户端
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXBUF 512
int main(int argc, char **argv)
{
int listen_fd, conn_fd; /*定义监听套接字和连接套接字*/
struct sockaddr_in serv_addr, client_addr; /*定义服务器地址和客户端地址*/
socklen_t len;
int port;
time_t t; /*定义time_t类型的变量表示时间*/
struct tm stime; /*定义一个日历时间的结构体*/
char req[MAXBUF+1] = {0}; /*接收请求字符串*/
char send_time[MAXBUF+1] = {0}; /*发送时间字符串*/
int z;
if(argc < 2) {
printf("Usage: %s portnumber\n", argv[0]);
exit(1);
}
if((port = atoi(argv[1])) < 0) {
printf("portnumber: 0~65535\n");
exit(1);
}
/*
* 创建套接字
*/
if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket fail");
exit(1);
}
/*
* 设置服务器地址
*/
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
/*
* 绑定套接字
*/
if(bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("bind fail");
exit(1);
}
/*
* 监听
*/
if(listen(listen_fd, 128) == -1) {
perror("listen fail");
exit(1);
}
while(1) {
len = sizeof(client_addr);
/*接收一个客户端的连接并创建连接套接字*/
if((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &len)) == -1) {
perror("accept fail");
exit(1);
}
fprintf(stdout, "Server get connection from %s: %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
/*处理客户端请求*/
while(1) {
z = read(conn_fd, req, MAXBUF);
if(z == -1) {
perror("read fail");
exit(1);
}
if(z == 0) {
/*z=0说明客户端关闭连接*/
fprintf(stdout, "client %s has closed the socket\n", inet_ntoa(client_addr.sin_addr));
close(conn_fd);
break;
}
req[z] = '\0';
time(&t);
stime = *localtime(&t);
/*
* 根据请求格式字符串生成相应时间字符串
*/
strftime(send_time, MAXBUF, req, &stime);
z = write(conn_fd, send_time, strlen(send_time));
if(z == -1) {
perror("write fail");
exit(1);
}
}
}
close(listen_fd);
return 0;
}
/*************************************************************************
> File Name: timeclient.c
> Author:
> Mail:
> Created Time: Tue 10 Apr 2018 11:03:48 AM CST
************************************************************************/
/*
* 客户端向服务器发送日期时间格式字符串
* 从服务器接收到时间对应的日期字符串后打印输出
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAXBUF 512
int main(int argc, char **argv)
{
int conn_fd; /*创建连接套接字*/
struct sockaddr_in serv_addr, client_addr; /*创建服务器地址和客户端地址*/
int port;
char req[MAXBUF+1] = {0};
char recv[MAXBUF+1] = {0};
size_t z;
if(argc < 3) {
printf("Usage: %s address portnumber\n", argv[0]);
exit(1);
}
if((port = atoi(argv[2])) < 0) {
printf("portnumber: 0~65535\n");
exit(1);
}
if((conn_fd = socket(AF_INET, SOCK_STREAM,0)) == -1) {
perror("sockt fail");
exit(1);
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(port);
if(connect(conn_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("connect fail");
exit(1);
}
fprintf(stdout, "\tclient connects to %s: %s\n", argv[1], argv[2]);
while(1) {
printf("\nEnter format string('q'uit?):");
if(fgets(req, MAXBUF, stdin) == NULL || req[0] == 'q') {
printf("\n");
break;
}
z = strlen(req);
if(z>0 && req[--z] == '\n') {
req[z] = '\0';
}
if(z == 0)
continue; /*客户端仅仅输入换行符*/
/*发送请求到服务器端*/
z = write(conn_fd, req, strlen(req));
if(z<0) {
perror("write fail");
exit(1);
}
fprintf(stdout, "\tclient has sent '%s' to the server\n", req);
/*从服务器端接收*/
if((z = read(conn_fd, recv, MAXBUF)) == -1) {
perror("read fail");
exit(1);
}
if(z == 0) {
fprintf(stdout, "server has closed the socket\n");
fprintf(stdout, "press any key to continue...\n");
getchar();
break;
}
recv[z] = '\0';
/*输出结果*/
printf("result from %s: %d \n\t'%s'\n",
inet_ntoa(serv_addr.sin_addr),
ntohs(serv_addr.sin_port),
recv);
}
close(conn_fd);
return 0;
}
运行服务器端:
$ ./timeserver 9000
Server get connection from 127.0.0.1: 57628
client 127.0.0.1 has closed the socket
运行客户端
$ ./timeclient 127.0.0.1 9000
client connects to 127.0.0.1: 9000
Enter format string('q'uit?):%Y %D
client has sent '%Y %D' to the server
result from 127.0.0.1: 9000
'2018 04/10/18'
Enter format string('q'uit?):q
上面的流式套接字服务器在对所接收到的一个客户端的连接请求进行处理时,不能再接收(执行accept函数)其他客户端的链接请求,只有当服务器完全结束了对这个客户端的所有请求处理后,才能对下一个客户端的请求进行处理。
可以利用Linux系统的多任务特性,通过创建子进程系统调用,让新产生的子进程对客户端请求进行后续处理,而主进程返回继续接收其他客户端发来的请求,这样就实现了同时对多个客户端请求的并行处理模式。
/*************************************************************************
> File Name: multi_server.c
> Author:
> Mail:
> Created Time: Tue 10 Apr 2018 01:03:19 PM CST
************************************************************************/
/*
* 这是一个多进程服务器
* 服务器主进程负责监听
* 子进程负责处理连接请求
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAXBUF 512
static void sigchild_handler(int signo)
{
pid_t pid;
int status;
char msg[] = "SIGCHLD caught\n";
write(STDOUT_FILENO, msg, sizeof(msg));
do {
pid = waitpid(-1, &status, WNOHANG);
}while(pid>0);
}
int main(int argc, char **argv)
{
int listen_fd, conn_fd; /*定义监听套接字和连接套接字*/
pid_t pid; /*进程标识符*/
struct sockaddr_in serv_addr, client_addr; /*定义服务器地址和客户端地址*/
socklen_t len;
int port;
time_t t;
struct tm stime;
char req[MAXBUF+1] = {0};
char send_time[MAXBUF+1] = {0};
int z;
struct sigaction child_action;
memset(&child_action, 0, sizeof(child_action));
child_action.sa_flags |= SA_RESTART;
child_action.sa_handler = sigchild_handler;
if(sigaction(SIGCHLD, &child_action, NULL) == -1) {
perror("Fail to ignore SIGCHLD");
}
if(argc < 2) {
fprintf(stdout, "Usage: %s portnumbr\n", argv[0]);
exit(1);
}
if((port = atoi(argv[1])) < 0) {
fprintf(stdout, "portnumber: 0~65535\n");
exit(1);
}
/*
* 创建监听套接字
*/
if((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket fail");
exit(1);
}
/*
* 设置服务器地址
*/
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
/*
* 绑定
*/
if(bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("bind fail");
exit(1);
}
/*
* 监听
*/
if(listen(listen_fd, 128) == -1) {
perror("listen fail");
exit(1);
}
while(1) {
len = sizeof(client_addr);
if((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &len)) == -1) {
perror("accept fail");
exit(1);
}
fprintf(stdout, "Server get connection from %s: %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
/*
* 创建子进程进行处理
*/
pid = fork();
if(pid == -1) {
perror("fork fail");
exit(1);
} else if(pid == 0) {
/*
* 子进程处理
*/
fprintf(stdout, "\tEntering the child: %d\n", getpid());
while(1) {
z = read(conn_fd, req, MAXBUF);
if(z == -1) {
perror("read fail");
exit(1);
}
if(z == 0) {
fprintf(stdout, "\tclient %s has close the socket\n",
inet_ntoa(client_addr.sin_addr));
break;
}
req[z] = '\0';
time(&t);
stime = *localtime(&t);
strftime(send_time, sizeof(send_time), req, &stime);
z = write(conn_fd, send_time, strlen(send_time));
if(z == -1) {
perror("write fail");
exit(1);
}
}
fprintf(stdout, "\tChild process: %d exits\n", getpid());
close(conn_fd);
exit(0);
} else {
/*
* 父进程
*/
fprintf(stdout, "This is parent\n");
close(conn_fd); /*关闭重复的套接字*/
}
}
close(listen_fd);
return 0;
}
服务器子进程循环处理客户端发来的请求,当客户端请求完毕断开链接后,服务器子进程的read操作将遇到EOF,从而导致子进程退出处理循环并关闭服务器的此连接套接字。子进程执行exit(0)后,内核将向父进程发送SIGCHLD信号,同时子进程进入僵死状态。
子进程终止时,父进程的相关善后处理:
运行服务器:
$ ./multi_server 9000
Server get connection from 127.0.0.1: 57698
This is parent
Entering the child: 3387
Server get connection from 127.0.0.1: 57700
This is parent
Entering the child: 3389
client 127.0.0.1 has close the socket
Child process: 3389 exits
SIGCHLD caught
client 127.0.0.1 has close the socket
Child process: 3387 exits
SIGCHLD caught
运行客户端1
$ ./timeclient 127.0.0.1 9000
client connects to 127.0.0.1: 9000
Enter format string('q'uit?):%c
client has sent '%c' to the server
result from 127.0.0.1: 9000
'Tue Apr 10 14:56:23 2018'
Enter format string('q'uit?):q
运行客户端2
$ ./timeclient 127.0.0.1 9000
client connects to 127.0.0.1: 9000
Enter format string('q'uit?):q