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

经过半天到努力,终于写好一个采用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 1024


void 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 port
	svr_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 client
	ret = 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	    255

int 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 server
	svr_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位机。所以传递二进制是很不明智的。如果非要传递二进制,却需要明确定义二进制格式(每项数据所占用的位数,大端、小端字节序的问题)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值