TCP网络编程之echo回射程序

TCP网络编程有三个例子最值得学习研究,分别是echo、chat、proxy,都是长连接协议。接下来,把这几个例子都实现。本节用一个简单的例子来讲TCP客户/服务器程序框架,这也是echo的实现。
程序的基本流程:

  1. 客户从标准输入键入一行文本,并发送给服务器。
  2. 服务器接收到文本之后回射给客户端。
  3. 客户端接收到服务器的文本,把它显示到标准输出上。

尽管下列实现代码很简单,但是它已经阐述了基本的tcp客户/服务器的框架,想要实现任何复杂的程序都可以以这个程序作为基本框架来开发。比如做成一问一答的方式,收到的请求和发送响应的内容不一样,这时候要考虑打包与拆包格式的设计,进一步还可以写简单的HTTP服务。以下贴出源码。后续以tcp socket的框架写个简单功能的聊天室。

服务器程序

//server_echo.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>

#define SERV_PORT        9877   
#define LISTENQ     1024
#define MAXLINE     4096

void str_echo(int sockfd)
{
    char buff[MAXLINE];
    int length=0;
    printf("server begin recv\n");
    while(length=recv(sockfd,buff,MAXLINE,0)) //这里是分包接收,每次接收4096个字节
    {
        if(length<0)
        {
            perror("recv");
            exit(-1);
        }
        printf("server send\n");
        if (send(sockfd,buff,MAXLINE,0) < 0)
        {
            perror("Send");
            exit(-1);
        }
        bzero(buff, sizeof(buff));
    }
}

int main(int argc, char **argv)
{
    int                 listenfd, connfd,fpid;
    pid_t               childpid;
    socklen_t           clilen;
    struct sockaddr_in  cliaddr, servaddr;
    //建立socket连接
    if ((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        perror("socket");
        exit(1);
    }
    printf("create socket success!\n");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family      = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port        = htons(SERV_PORT);

    // 设置套接字选项避免地址使用错误,为了允许地址重用,我设置整型参数(on)为 1 (不然,可以设为 0 来禁止地址重用)
    int on=1;  
    if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)  
    {  
        perror("setsockopt failed");  
        exit(-1);  
    }

    if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
    {
        perror("bind");
        exit(-1);
    }
    printf("Bind success!\n");
    if(listen(listenfd, LISTENQ) == -1)
    {
        perror("listen");
        exit(-1);
    }

    for ( ; ; ) 
    {
        clilen = sizeof(cliaddr);
        printf("begin accept!\n");
        if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0)
        {
            perror("accept");
            exit(-1);           
        }
        printf("begin fork!\n");
        fpid=fork();   
        if (fpid < 0)   
        {
            perror("fork");
            exit(-1);
        } 
        else if (fpid == 0) //child process
        {  
            close(listenfd);    // close listening socket
            str_echo(connfd);   // process the request
            exit(0);
        }
        close(connfd);          // parent closes connected socket
    }
}

客户端程序

//client_echo.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>

#define MAXLINE     4096
#define SERV_PORT        9877   

void str_cli(FILE *fp, int sockfd)
{
    char    sendline[MAXLINE], recvline[MAXLINE];

    while(fgets(sendline, MAXLINE, fp) != NULL)
    {
        printf("sendline : %s\n",sendline);
        if (send(sockfd,sendline,strlen(sendline),0) < 0)
        {
            perror("Send");
            exit(-1);
        }
        if (recv(sockfd,recvline,MAXLINE,0) < 0 )
        {
            perror("recv");
            exit(-1);
        }
        printf("recvline : %s\n",recvline);
        fputs(recvline, stdout);
    }
}

int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in  servaddr;

    if (argc != 2)
    {
        perror("usage: tcpcli <IPaddress>");
        exit(-1);
    }


    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd");
        exit(-1);
    }
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
    {
        perror("inet_pton");    
        exit(-1);       
    }

    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(-1);       
    }

    str_cli(stdin, sockfd);

    exit(0);
}

测试结果如下:

ubuntu:~/test/1214-test$ ./server_echo 
create socket success!
Bind success!
begin accept!
begin fork!
begin accept!
server begin recv
server send
ubuntu:~/test/1214-test$ ./client_echo 192.168.65.1
helloworld
sendline : helloworld

recvline : helloworld

helloworld

调试问题:

在调试程序的时候,我发现如果服务器被断开了,再重新启动,会出现如下错误:

bind: Address already in use

查了之后,才知道这是由于套接字处于TIME_WAIT状态引起的,这个时间是几分钟,过后再重新启动服务器就没问题了。有时候,我们在调试程序的时候,为了允许地址重用,可以在bind前加上这两句话可以避免这种状况,设置整型参数 on 为 1 (不然,可以设为 0 来禁止地址重用)。

int on=1;  
if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)  
{  
     perror("setsockopt failed");  
     exit(-1);  
}

猜你喜欢

转载自blog.csdn.net/u014530704/article/details/78731505