5 TCP客户端/服务器程序实例

来源:互联网 发布:知乎 张居正 编辑:程序博客网 时间:2024/04/29 17:35

经过半天到努力,终于写好一个采用fork子进程方法编写到tcp服务器,直接上代码。


tcp server:

/* socksrv.c*/#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h> /* for struct sockaddr_in*/#include <sys/errno.h>#include <signal.h>#define SVR_IP   "127.0.0.1"#define SVR_PORT  1234#define BACKLOG   10#define MAX_BUF_LEN 1024void chld_handle(int sig){    pid_t   pid;    int     stat;    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)    {        printf("child %d terminated\n", pid);    }    return;    }int main(){    struct sigaction act;    act.sa_handler = chld_handle;    sigemptyset(&act.sa_mask);    act.sa_flags = SA_RESTART;
    sigaction(SIGCHLD, &act, 0);    int listenfd    = 0;    int connfd      = 0;   int ret         = 0;struct sockaddr_in svr_addr, cli_addr;    memset(&svr_addr, 0, sizeof(struct sockaddr_in));    memset(&cli_addr, 0, sizeof(struct sockaddr_in));      // create listen socket fd    listenfd = socket(AF_INET, SOCK_STREAM, 0);        if (listenfd == -1) {perror("socket failed");        exit(1);}// bind svr ip and portsvr_addr.sin_family = AF_INET;                svr_addr.sin_port = htons(SVR_PORT);            svr_addr.sin_addr.s_addr = inet_addr(SVR_IP); ret = bind(listenfd, (struct sockaddr*)&svr_addr, sizeof(struct sockaddr));if (ret == -1) {perror("bind failed");exit(1);}    // listen clientret = listen(listenfd, BACKLOG); if (ret == -1) {perror("listen failed");exit(1);}printf("server is on...\n");    while(1)    {        int sin_size = sizeof(struct sockaddr_in);        if((connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &sin_size)) < 0)        {            perror("accept failed");            if (errno == EINTR)            {                perror("child process cause it\n");                continue;            }            else            {                break;            }        }        pid_t child_pid = fork();        if (0 == child_pid) // this is child process        {            close(listenfd);            printf("one socket(%d) client come\n", connfd);                       char recv_buf[MAX_BUF_LEN] = {0};            int recv_len = 0;            while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)            {                printf("svr %d recv %s recv_len %d\n", connfd, recv_buf, recv_len);                ret = send(connfd, recv_buf, strlen(recv_buf), 0);                if (ret < 0)                {                    perror("send failed");                    exit(1);                }                memset(recv_buf, 0, sizeof(recv_buf));            }                       if (recv_len < 0)            {                perror("recv failed");                exit(1);            }            printf("one socket(%d) client go\n", connfd);            close(connfd);            exit(0);        }        close(connfd);    }    close(listenfd);return 0;}


tcp client:

/* sockclnt.c*/#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h> /*for struct sockaddr_in*/#define SVR_IP      "127.0.0.1"#define SVR_PORT    1234#define BUFSIZE    255int main(){int ret     = 0;int sockfd  = 0;    struct sockaddr_in svr_addr;    memset(&svr_addr, 0, sizeof(struct sockaddr_in));    char * msg = "'path:/home/dongshen/media_file/b_1.ts'";    // create client socket fd    sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("socket failed");exit(1);}    // connet to serversvr_addr.sin_family         = AF_INET;                 svr_addr.sin_port           = htons(SVR_PORT);          svr_addr.sin_addr.s_addr    = inet_addr(SVR_IP);     ret = connect(sockfd, (struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));if (ret == -1) {perror("connect failed");exit(1);}    int i = 0;    for (i = 0; i < 5; i++)    {        char * msg = "hello server....";        printf("client send msg: %s\n", msg);if((send(sockfd, msg,strlen(msg), 0))<0)        {perror("send()");        }        char recv_buf[BUFSIZE] = {0};        int recv_len = recv(sockfd, recv_buf, sizeof(recv_buf), 0);        printf("client recv msg: %s%d\n", recv_buf, recv_len);        sleep(10);    }       close(sockfd);}

对于采用fork子进程编写的tcp server,有如下几点需要注意

1. 主进程要关闭子进程的connfd,子进程要关闭主进程到listenfd,原因是,在采用fork方法fork出子进程之后,这俩fd都会在每个进程中有个副本,如果不关闭,则会造成和客户端到连接无法正常关闭。

2. 对于子进程一定要采用sigaction方法截获SIGCHLD信号,并采用waitpid来彻底终止子进程,否则子进程将编程僵尸进程,而且一定要采用waitpid,不能采用wait,因为如果同一时间有多个子进程同时关闭(如多个客户端同时关闭),则会造成大量SIGCHLD信号,由于sigaction会对信号堵塞,同时多个排队信号,会进行合并处理,会造成信号丢失,产生僵尸进程,如果采用waitpid,则会同一时间循环处理掉已经结束的子进程。

3. 在信号SIGCHLD来临之际,主进程正堵塞在accept,中断后,在有些系统,accept不能由内核重启,造成accept返回错误EINTR,所以需要对其特殊处理,continue,手动重启accept,但是在centos系统中,经过测试当将sigaction中的sa_flags设置为SA_RESTART,中断到accept会被内核自动重启,无此顾虑


下面让我们来讨论几种服务器异常情况:

1、服务器进程终止

服务器进程终止,那么在终止时刻,socket fd会正常关闭,也就是说内核会往客户端发送FIN消息。此时

如果客户端进程没有堵塞与read,而是仍然write数据给服务器,则服务器会返回RST消息;

如果客户端连续多次write调用,第一个write引起服务器RST消息,客户端内核收到RST消息后,进程仍然write数据,内核会向进程发送SIGPIPE信号,该信号默认行为为终止进程,同时write会返回EPIPE错误。那么是否捕获该信号,则由于具体业务决定。

2、服务器主机崩溃

如果服务器主机突然崩溃(不是正常关机),这个时候客户端再发送数据,则会产生超时重传,或者返回ETIMEOUT、EHOSTUNREACH、ENETUNREACH错误。

3、服务器主机崩溃并重启

这个时候客户端如果在发送数据给服务端,服务端则会响应一个RST消息。

4、服务器主机关机

如果主机关机,init会想所有进程发送SIGTERM信号,等待5~20s,然后向所有活着的进程发送SIGKILL信号,这样服务进程会想客户端发送FIN消息。

总结:从刚刚这几个服务端的异常可以看出,客户端有几件事很重要:

1、探测服务器是否还活着很重要,SO_KEEPALIVE套接字选项。

2、一旦服务器发送FIN或RST,客户端能及时感知到,通过select和epoll。


传送的数据格式

比如传递两个数字,有下面两种方法。

1、字符串,客户端和服务端分别维护一段buf,传递字符串,a+空格+b,然后到服务端在解析该字符串

2、二进制,定义一个结构体,直接传递结构体变量指针,数据长度为结构体长度

struct

{

long a;

long b;

}

<注意>:如果传递二进制,则需要考虑两点主机字节序的问题,并且如果为long型,还需要考虑是32位机还是64位机。所以传递二进制是很不明智的。如果非要传递二进制,却需要明确定义二进制格式(每项数据所占用的位数,大端、小端字节序的问题)


原创粉丝点击