Linux TCP程式設計常見問題考慮

嚇人的猿發表於2018-03-01

1 搭建一個TCP伺服器

原始碼:tcp_server.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAXBYTES 1024
#define SERVER_PORT 8888
int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, clifd;
    char str[INET_ADDRSTRLEN];
    char buf[MAXBYTES];
    int i, n, len;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERVER_PORT);
    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    listen(listenfd, 128);
    printf("Accepting connections ...\n");
    while (1) 
    {
        bzero(&buf, sizeof(buf));
        cliaddr_len = sizeof(cliaddr);
        clifd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        if(clifd == -1)
        {
            perror("clifd");
            continue;
        }
        len = read(clifd, buf, sizeof(buf) - 1);
        if(len != -1)
        {
            printf("accept from %s at PORT %d :[%d] %s\n"
                , inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str))
                , ntohs(cliaddr.sin_port), len, buf);
        }
        else
        {
            perror("read");
        }
        close(clifd);
    }
    return 0;

測試伺服器是否好用可以使用一下命令:

nc 127.0.0.1 8888

2 搭建一個TCP客戶端

原始碼:tcp_client.c

#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
    struct sockaddr_in serveraddr;
    int sockfd, n, ret;
    char buf[] = "helloworld";
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
    serveraddr.sin_port = htons(SERVER_PORT);
    ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if(ret == -1)
      perror("connect");
  
    ret = write(sockfd, buf, strlen(buf));
    if(ret == -1)
      perror("connect");
    close(sockfd);
    return 0;
}

需要注意的事:

​ 1.當socket寫端關閉的時候,讀端read將一直讀取到0位元組,可以使用這一特性來關閉讀端套接字。

​ 2.當socket讀端關閉的時候,寫端write繼續寫入資料,將會報錯,並且收到一個SIGPIPE訊號。

​ 3.當網路異常的時候(如拔掉網線),這個時候read不會返回錯誤,將阻塞等待資料;write也不會返回錯誤,仍然可以寫入資料,直到寫buffer填滿,將阻塞write函式。

​ 4.socket有個buffer,並且這個buffer的大小是一定的,當一直往socket write的時候,達到這個buffer的最大值後,write將阻塞。

伺服器改寫如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#define MAXBYTES 1024
#define SERVER_PORT 8888
int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, clifd;
    char str[INET_ADDRSTRLEN];
    char buf[MAXBYTES];
    int i, n, len;
  
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERVER_PORT);
    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    listen(listenfd, 128);
    printf("Accepting connections ...\n");
    while (1) 
    {
        bzero(&buf, sizeof(buf));
        cliaddr_len = sizeof(cliaddr);
        clifd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); //可能被訊號中斷需要注意
        if(clifd == -1)
        {
            perror("read");
            return -1;
        }   
        while(1)
        {
            len = read(clifd, buf, sizeof(buf) - 1);
            if(len > 0)
            {
                printf("accept from %s at PORT %d :[%d] %s\n"
                        , inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str))
                        , ntohs(cliaddr.sin_port), len, buf);
            }
            else if(len == 0) //寫端關閉的時候將永遠讀到0位元組,如果讀到0位元組就可以關閉套接字了
            {
                close(clifd);
                break;
            }
            else
            {
                perror("read");
            }
            sleep(1);
        }
        
    }   
    close(listenfd);
    return 0;

}

客戶端改寫如下:

#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#define SERVER_PORT 8888
void sig_handler(int sig)
{
    printf("SIGPIPE\n");
}

int main(int argc, char *argv[])
{
    signal(SIGPIPE, sig_handler); //當讀端關閉的時候,如果繼續寫,則會觸發SIGPIPE訊號
    struct sockaddr_in serveraddr;
    int sockfd, n, ret; 
    char buf[] = "helloworld";
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
    serveraddr.sin_port = htons(SERVER_PORT);

    int conntimes = 0;
    while(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) //有可能連結失敗,通過這種方式定時連結
    {
        if(conntimes++ > 10)
          return -1;
        sleep(1);
        perror("connect");
        
    }

    int num = 0;
    while(1)
    {
        ret = write(sockfd, buf, strlen(buf));
        if(ret == -1)
        {
            perror("write");
        }
        else
            num += ret;
        printf("send bytes %d\n", num);
        sleep(1); //該值根據需要進行更改。
    }
    close(sockfd);
    return 0;
}

相關文章