1. 程式人生 > >TCP客戶/服務器簡單Socket程序

TCP客戶/服務器簡單Socket程序

main 字段 sockaddr 需要 apple 必須 一個 bin 可能

建立一個 TCP 連接時會發生下述情形:

1. 服務器必須準備好接受外來的連接。這通常通過調用 socket、bind 和 listen 這三個函數來完成,我們稱之為被動打開。

2. 客戶通過調用 connect 發起主動打開,這導致客戶TCP發送一個SYN(同步)分節,標識希望連接的服務器端口以及初始序號。通常SYN分節不攜帶數據,其所在IP數據報只含有一個IP首部、一個TCP首部及可能有的TCP選項。

3. 服務器發送回一個包含服務器初始序號以及對客戶端 SYN 段確認的 SYN + ACK 段作為應答,由於一個 SYN 占用一個序號,因此確認序號設置為客戶端初始序號加 1。

4. 客戶端發送確認序號為服務器初始序號加 1 的 ACK 段,對服務器 SYN 段進行確認。

這種交換至少需要三個分組,因此稱之為TCP的三路握手。

技術分享圖片

一旦TCP建立連接,客戶/服務器之間便可以進行數據通信。

1. 服務器端

技術分享圖片
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include <errno.h>  
#define PORT 6666

int main(int argc,char **argv)
{
    int ser_sockfd,cli_sockfd;
    int err,n;
    int addlen;
    struct sockaddr_in ser_addr;
    struct sockaddr_in cli_addr;
    char recvline[200],sendline[200];
    
    ser_sockfd = socket(AF_INET,SOCK_STREAM,0);          //創建套接字
    if(ser_sockfd == -1)
    {
        printf("socket error:%s\n",strerror(errno));
        return -1;
    }
    
    bzero(&ser_addr,sizeof(ser_addr));
    
    /*在待捆綁到該TCP套接口(sockfd)的網際套接口地址結構中填入通配地址(INADDR_ANY)
    和服務器的眾所周知端口(PORT,這裏為6666),這裏捆綁通配地址是在告知系統:要是系統是
    多宿主機(具有多個網絡連接的主機),我們將接受宿地址為任何本地接口的地址*/     
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    ser_addr.sin_port = htons(PORT);
    
    //將網際套接口地址結構捆綁到該套接口
    err = bind(ser_sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));  
    if(err == -1)
    {
        printf("bind error:%s\n",strerror(errno));
        return -1;
    }
    //將套接口轉換為一個監聽套接口,監聽等待來自客戶端的連接請求
    err = listen(ser_sockfd,5);                                      
    if(err == -1)
    {
        printf("listen error\n");
        return -1;
    }
    
    printf("listen the port:\n");
    
    while(1)
    {    
        addlen = sizeof(struct sockaddr);
        //等待阻塞,等待客戶端申請,並接受客戶端的連接請求
        //accept成功,將創建一個新的套接字,並為這個新的套接字分配一個套接字描述符
        cli_sockfd = accept(ser_sockfd,(struct sockaddr *)&cli_addr,&addlen);   
        if(cli_sockfd == -1)
        {
            printf("accept error\n");
        }
        
        //數據傳輸
        while(1)
        {
            printf("waiting for client...\n");
            n = recv(cli_sockfd,recvline,1024,0);
            if(n == -1)
            {
                printf("recv error\n");
            }
            recvline[n] = \0;
            
            printf("recv data is:%s\n",recvline);
            
            printf("Input your words:");
            scanf("%s",sendline);
            send(cli_sockfd,sendline,strlen(sendline),0);
        }
        close(cli_sockfd);
    }
    close(ser_sockfd);
    
    return 0;
}
技術分享圖片

1.首先通過 socket 函數創建套接字,此時套接字數據結構字段並未填充,在使用之前必須調用過程來填充對應字段,這裏在地址結構中填入通配地址(INADDR_ANY),通配地址就是指定地址為 0.0.0.0 的地址,表示服務器接受機器上所有IP地址的連接,用於多IP機器上。這樣無論客戶 connect 哪個IP地址,服務器端都會接收到請求,即接受宿地址為任何本地接口的地址。如果是指定地址,那麽機器只有 connect 這個地址才能成功。後面是填充端口號,如果指定為 0,則由系統隨機選擇一個未被使用的端口。

2. bind 將沒有指定端口的 socket(ser_sockfd)綁定到我們指定的端口上(通配地址+指定端口號),服務器是通過它們的眾所周知端口被大家認識的。這樣 socket(ser_sockfd)就與指定的端口產生了關聯,即指向了指定端口。

3. listen 將套接口轉換為一個監聽套接口,被動打開,允許監聽客戶端的連接請求,然後 accept 客戶端的連接請求,沒有請求則阻塞。

5. accept 成功後,將創建新的套接字,並為新套接字分配一個套接口描述符,該套接字除了記錄本地(服務器)的IP和端口號信息外,還記錄了目的(客戶)IP和端口號信息。服務器與客戶的通信則是通過該新創建的套接字(已連接套接字)進行。


2. 客戶端

技術分享圖片
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#define PORT 6666

int main(int argc,char **argv)
{
    int sockfd;
    int err,n;
    struct sockaddr_in addr_ser;
    char sendline[200],recvline[200];
    
    sockfd = socket(AF_INET,SOCK_STREAM,0);       //創建套接字
    if(sockfd == -1)
    {
        printf("socket error\n");
        return -1;
    }
    
    bzero(&addr_ser,sizeof(addr_ser));     
    
    /*用通配地址和指定端口號裝填一個網際接口地址結構*/ 
    addr_ser.sin_family = AF_INET;
    addr_ser.sin_addr.s_addr = htonl(INADDR_ANY);   
    addr_ser.sin_port = htons(PORT);                
    
    //TCP:客戶(sockfd)向服務器(套接口地址結構)發起連接,主動請求
    //服務器的IP地址和端口號有參數addr_ser指定
    err = connect(sockfd,(struct sockaddr *)&addr_ser,sizeof(addr_ser));   
    if(err == -1)
    {
        printf("connect error\n");
        return -1;
    }
    
    printf("connect with server...\n");
    
    //數據傳輸
    while(1)
    {     
        printf("Input your words:");
        scanf("%s",sendline);
        
        send(sockfd,sendline,strlen(sendline),0);            
    
        printf("waiting for server...\n");
    
        n = recv(sockfd,recvline,100,0);                      
        recvline[n] = \0; 
        
        printf("recv data is:%s\n",recvline);
    }
    
    return 0;
}
技術分享圖片

1. 客戶端同樣通過 socket 創建套接字,TCP 客戶通常不把IP地址捆綁到它的套接口上,當連接套接口時,內核將根據所用外出網絡接口來確定源IP地址,並選擇一個臨時端口作為源端口。
2. 用通配地址和指定端口填充的是待連接服務器端的套接字地址結購,這裏采用的是通配地址,由於服務器端指定的是通配地址,即接受機器上所有IP地址的連接,同樣客戶也可向機器上任何IP地址發起連接,服務器端都會接收到。

3. 客戶端向服務器發起連接請求,connect 成功後,其請求連接的服務器的IP和端口號信息將會寫入該套接字,這樣該套接字也同時記錄了本地和目的的IP地址和端口信息。也就可以進行通信了。

結果如下:

技術分享圖片

TCP客戶/服務器簡單Socket程序