簡單的tcp socket編程及分析

來源:互聯網
上載者:User

最近沒事幹研究了一下socket編程,自己寫了一個簡單的tcp server和tcp client,發現對tcp協議有了很多新的理解。

廢話不說,先上代碼:

tcp_client.c:

 1
#include
<stdio.h>

 2
#include
<stdlib.h>

 3
#include
<sys/socket.h>
 /*
 socket, connect, etc.
*/

 4
#include
<arpa/inet.h>
 /*
 inet_aton, sockaddr_in, etc
*/

 5
#include
<string.h>
 /*
 memset
*/

 6

 7
#define BUFFSIZE
1024

 8
typedef
 struct
 sockaddr SA;
 9

10
int
 main(int
 argc, char
** argv)
11
{
12
    int
 clientfd, port = 1234
;
13
    struct
 sockaddr_in server_sockaddr;
14
    char
 buf[BUFFSIZE];
15
    const
 char
* server_ip = "10.0.2.15"
;
16
    int
 received = 0
;
17
    int
 errcode = 0
;
18
    /*
 create stream socket
*/

19
    if
 ((clientfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0
) {
20
        printf("failed to create socket
/n
"
);
21
        exit(-1);
22
    }
23

24
    /*
 zeroize and set server socket address
*/

25
    memset(&server_sockaddr, 0
, sizeof
(struct
 sockaddr_in));
26
    server_sockaddr.sin_family = AF_INET;
27
    server_sockaddr.sin_port = htons(port);
28
    inet_aton(server_ip, &server_sockaddr.sin_addr);
29

30
    /*
 connect client to server
*/

31
    /*
 connect: sends SYN, when server returns SYN,ACK, it sends ACK and returns
*/

32
    if
((errcode = connect(clientfd, (SA*)&server_sockaddr, sizeof
(server_sockaddr))) == 0
) {
33
        printf("connected.
/n
"
);
34
    }
35
    else
 {
36
        fprintf(stderr
, "error! connection refused.
/n
"
);
37
        exit(-1);
38
    }
39

40
    /*
 send message
*/

41
    while
 ((fgets(buf, BUFFSIZE, stdin
)) != NULL
) {
42
        /*
 wirte: PSH(push), ACK and send data
*/

43
        write(clientfd, buf, strlen(buf)-1); /*
 do not send the trailing '/n'
*/

44
        //printf(
"haha"
);

45
    }
46
    /*
 close: FIN, ACK
*/

47
    close(clientfd);
48

49
    return
 0
;
50
}

tcp_server.c:

 1
#include
<stdio.h>

 2
#include
<stdlib.h>
 /*
 exit
*/

 3
#include
<sys/socket.h>
 /*
 socket, connect, etc.
*/

 4
#include
<arpa/inet.h>
 /*
 inet_aton, sockaddr_in, etc
*/

 5
#include
<string.h>
 /*
 memset
*/

 6

 7
typedef
 struct
 sockaddr SA;
 8
#define LISTENQ
1024

 9
#define BUFFSIZE
1024

10

11
int
 main(int
 argc, char
** argv)
12
{
13
    int
 listenfd, commfd, port = 1234
, clientlen, optval=1
;
14
    char
 msg[BUFFSIZE];
15
    struct
 sockaddr_in server_sockaddr, client_sockaddr;
16

17
    clientlen = sizeof
(client_sockaddr);
18
    /*
 create stream socket
*/

19
    if
 ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0
) {
20
        fprintf(stderr
, "error! cannot create socket.
/n
"
);
21
        exit(-1);
22
    }
23

24
    /*
 zeroize and fill server sockaddr
*/

25
    memset(&server_sockaddr, 0
, sizeof
(server_sockaddr));
26
    server_sockaddr.sin_family = AF_INET;
27
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
28
    server_sockaddr.sin_port = htons(port);
29

30
    /*
 configure the server so that it can be restarted immediately
*/

31
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const
 void
*)&optval, sizeof
(int
));
32

33
    /*
 bind socket to inaddr
*/

34
    bind(listenfd, (SA *)&server_sockaddr, sizeof
(server_sockaddr));
35

36
    /*
 listen on listenfd
*/

37
    listen(listenfd, LISTENQ);
38

39

40
    while
(1
) {
41
        /*
 accept incoming requests
*/

42
        /*
 accpet: SYN, ACK
*/

43
        commfd = accept(listenfd, (SA* )&client_sockaddr, &clientlen);
44
        /*
 read: ACK
*/

45
        while
(read(commfd, msg, BUFFSIZE) > 0
) {
46
            printf("received:
/"
%s
/"/n
"
, msg);
47
            fflush(stdout
);
48
        }
49
        printf("EOF encountered
/n
"
);
50
        /*
 close: FIN ACK
*/

51
        close(commfd);
52
    }
53

54

55
    return
 0
;
56
}

討論:

tcp client/server 的一般編寫步驟:

tcp client的套路:socket -> connect -> send/receive data -> close

tcp server的套路:socket -> bind -> listen -> accept -> send/receive data -> close

在編寫tcp client時,首先使用socket函數建立一個新的socketfd(socket file
descriptor)。socket函數的第一個參數永遠是AF_INET(Address Family
Internet),表示為一個internet
socket;第二個參數為該socket的類型(這裡為SOCK_STREAM,即流類型(類似於TCP型);第三個參數為協議類型(在ip包中的
8bit protocol
type),這個在/usr/include/linux/in.h中有定義,IPPROTO_IP的值為0,其實就是TCP.
另外還有IPPROTO_TCP, IPPROTO_UDP等等。

開啟socketfd後,我們將伺服器的ip address,port(合稱socket address)填入一個sockaddr_in結構體中,並調用connect函數。

connect函數是我們這裡的重頭戲。如果使用wireshark來抓包可以發現,當client程式運行到connect時,會發出一個
(SYN),而server會返回一個(SYN
ACK)表示已收到(SYN),最後connect函數會發出一個(ACK)表示已收到(ACK)並返回0。這就是通常說的tcp中的三向交握
(three-way handshake).接下來我們就可以對socketfd進行讀寫操作了(也就是對伺服器發/收資料)。

這裡另外提一下,如果server根本不通(ping不到),則在抓包的時候一段時間後會看到ICMP包,內容為錯誤(destination
unreachable),connnect返回-1.
如果server可以連通,但是沒有開放相應的連接埠(這裡為1234),則(SYN)還是會發出,而server會返回一個(ACK
RST),表示拒絕串連,connect仍然返回-1。由此可以知道,只有在物理上能夠串連的基礎上,才會發出(SYN)的請求.

當connect返回0時(同時server的accept函數返回0),我們可以說串連已經建立了。這時候就可以收發資料了。我們在這兩個例子裡
面用的是裸unix系統調用(read和write)來對socketfd進行操作。當寫操作完成時,我們可以使用close()來關閉串連,這時
client發出(FIN ACK),server在接收到之後會發出(ACK)來關閉串連。(這種情況為client發出的關閉串連).

我們再來看server端:首先還是調用socket函數來建立socket,
然後將server的ip,port填入一個sockaddr_in結構。接下來使用bind()將sockaddr與socket進行綁定(對比
client程式中為connect),接著使用listen使該socket變為listen模式(預設socket為主動串連模式)。這時使用
accept()函數。accept函數的功能就是一直等待某個連接埠的串連,當收到(SYN)請求時回複(SYN
ACK)並等待,然後當收到(ACK)時返回。與前面client的討論聯絡起來我們就能夠完整瞭解串連的建立過程。

當串連建立以後,後面的操作就和client相似了:調用read/write進行資料的發送/接收,調用close()進行串連的斷開。這種情況下為伺服器主動中斷連線,發送FIN ACK,而client接受以後回複ACK關閉串連.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.