1. 程式人生 > >Linux網路程式設計:TCP客戶/伺服器模型及基本socket函式

Linux網路程式設計:TCP客戶/伺服器模型及基本socket函式

TCP客戶/伺服器模型

這裡寫圖片描述

TCP連線的分組交換

這裡寫圖片描述
在使用socket API的時候應該清楚應用程式和TCP協議棧是如何互動的:
呼叫connect()會發出SYN段(SYN是TCP報文段頭部的一個標誌位,置為1)
阻塞的read()函式返回0就表明收到了FIN段
客戶端呼叫connect()發起連線,伺服器端accept()返回套接字的時候就意味著TCP的三次握手已經完成

TCP11種狀態:除了圖中的10種狀態之外,還有一種狀態叫CLOSING,產生的原因是雙方同時關閉。

socket函式

建立一個套接字用於通訊

#include <sys/types.h>  
#include <sys/socket.h>  
int socket(int domain, int type, int protocol);  

引數:
domain:指定通訊協議族(protocol family),常用取值AF_INET(IPv4)
type:指定socket型別, 流式套接字SOCK_STREAM,資料報套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol:協議型別,常用取值0, 使用預設協議

返回值: 成功: 返回非負整數,套接字; 失敗: 返回-1

bind函式

繫結一個本地地址到套接字

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

引數:
sockfd:socket函式返回的套接字
addr:要繫結的地址

listen函式

listen函式應該用在呼叫socket和bind函式之後, 並且用在呼叫accept之前, 用於將一個套接字從一個主動套接字轉變成為被動套接字。

int listen(int sockfd, int backlog);  

引數backlog說明:
對於給定的監聽套介面,核心要維護兩個佇列:
1、已由客戶發出併到達伺服器,伺服器正在等待完成相應的TCP三路握手過程(SYN_RCVD狀態)
2、已完成連線的佇列(ESTABLISHED狀態)
但是兩個佇列長度之和不能超過backlog。(具體內容在UNP書裡有)
這裡寫圖片描述

accept函式:

從已完成連線佇列返回第一個連線,如果已完成連線佇列為空,則阻塞。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

引數:
sockfd:伺服器套接字
addr:將返回對等方的套接字地址, 不關心的話, 可以設定為NULL
addrlen:返回對等方的套接字地址長度, 不關心的話可以設定成為NULL, 否則一定要初始化

返回值: 成功則返回非負描述符,出錯則返回-1

connect函式

建立一個連線至addr所指定的套接字

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

引數:
sockfd:未連線套接字
addr:要連線的套接字地址
addrlen:第二個引數addr長度

簡單的回射客戶/伺服器模型

這裡寫圖片描述

簡單的回射客戶/伺服器例子

//echosrv.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int main(void)
{
        //建立一個套接字
        int listenfd; 
        if((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTP_TCP)) < 0) 
                ERR_EXIT("socket");
                
	    //初始化地址
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        
        //在bind之前開啟“地址重複利用”(設定套接字選項SO_REUSEADDR避免TIME_WAIT狀態的出現)
        int on = 1;
        if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
                ERR_EXIT("setsockopt");
                
        //繫結    
        if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
                ERR_EXIT("bind");

        //監聽    
        if (listen(listenfd, SOMAXCONN) < 0)
                ERR_EXIT("listen");     
        
        struct sockaddr_in peeraddr;//對方地址
        socklen_t peerlen = sizeof(peeraddr);
        int conn;//已連線套接字

        //接受連線
        if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
                ERR_EXIT("accept");                

        //連線成功後打印出對方的地址    
        printf("客戶端的ip = %s, port = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

        //處理通訊細節  
        char recvbuf[1024];
        while (1)
        {
                memset(recvbuf, 0, sizeof(recvbuf));
                int ret = read(conn, recvbuf, sizeof(recvbuf));  
                fputs(recvbuf, stdout);
                write(conn, recvbuf, ret);
        }

	    //關閉套接字
        close(conn);
        close(listenfd);

        return 0;
}                    
//echocli.c
#include <stdio.h>
#include <sys/types.h> 
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int main(void)
{    
        //建立套接字
        int sock; 
        if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)  
                ERR_EXIT("socket");
        
        //初始化所要連線的對方地址
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(5188);
        servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        //連線
        if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0)   
                ERR_EXIT("connect");
                 
        char sendbuf[1024] = {0};
        char recvbuf[1024] = {0};
        //通訊細節
        while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 
        {
                write(sock, sendbuf, strlen(sendbuf));
                read(sock, recvbuf, strlen(recvbuf));
                fputs(recvbuf, stdout);

                memset(sendbuf, 0, sizeof(sendbuf));
                memset(recvbuf, 0, sizeof(recvbuf));
        }
        
	    close(sock);

        return 0;
}