閱讀228 返回首頁    go 搜狐


套接字編程簡介

套接字編程簡介

項目:UNIX網絡編程學習

作者:曾金龍

供職:(深圳迅雷網絡技術股份有限公司)

領域:迅雷下載庫研發

日期:2014-07-25


1, TCP連接圖

socket編程,過眼煙雲的去看,無外乎就那麼幾個API,但是,如果想登堂入室,必須注重裏麵的每一個細節。


對於TCP編程而言,最重要的是記住這麼一幅圖。死記的基礎上理解。

windows下有visio,ubuntu下隻好用Dia,不是很習慣,而且不支持中文輸入。


圖1 TCP連接的數據包交換圖

你特別需要注意的是:

1)哪個地方會被阻塞住,例如讀取數據的時候,如果對方沒有寫數據,則會被阻塞,對方要是關閉了連接則會讀取到0(EOF);

2)在每一個階段如果順序發生了變化,將會是怎麼樣。例如,我們總是習慣對方寫,我們讀,然後我們寫,對方讀,如果不讀怎麼樣?或者一直寫。write是很少會被阻塞的,除非寫滿了了內核的緩衝區,free空間比它的下限還要小。當然一旦free空間大於其下限,Epoll等待的時候是會被喚醒的,EPOLLOUT,表示可以寫。

3)如果彼此在不同的時機宕機了,脫離網絡了,拒絕訪問了,關閉了等,又將會發


生什麼?會有一些是TIME_OUT,一些是會受到RST,一些是受到FIN。這個就是TCP的細節。而細節決定成敗。

在迅雷的網絡模組中,已經把socket打包在公共庫裏麵,配合異步框架使用。前輩們的代碼還是很優秀的。特別是用select ,poll,epoll,配合signal,pipe,mutex等這些實現的異步框架,是迅雷架構裏麵最優美的代碼之一。(迅雷用C實現C++的代碼也值得細品)


2 地址結構

編程要用到的地址結構就是這個,再在用socket函數的時候強製類型轉換成struct sockaddr

struct in_addr{in_addr_t    s_addr;};

struct sockaddr_in{
uint8_t    sin_len;
sa_family_t    sin_family;
in_port_t    sin_addr;
char    sin_zero[8];
};

struct sockaddr{
uint8_t    sa_len;
sa_family_t sa_family;
char sa_data[14];
};

其他的一般還用不到,用到再查閱。

對了,有些博客你會發現它們寫的結構不太一樣,就是沒有開頭的sin_len和sa_len,這個問題在書中有說過,為了兼容了ISO,後來在BSD4.3的時候加入的,其實,作為一個數據結構,在頭部加一個本結構的長度,就實現了該結構可變長。

3 主機--網絡字節順序問題

不同的主機它存在大小端問題,所以在網絡中交換的數據,必須處理大小端不一致的問題。

3.1)如何檢測自己的機子是大端還是小端


書中定義了一個union聯合體,其實,不用。

BOOL isBigEndian()
{
     short  v=0x0102;
     char* c=(char*)&v;
     if(c[0]==0x01&&c[1]==0x02)return TRUE;
     else return FALSE;
}

3.2)主機字節順序和網絡順序之間的轉換函數

host to network short(long)
network to host short(long)
就這四個函數。
#include <netinet/in.h>
uinit16_t htons(uint16_t host16bitvalue);
uinit32_t htonl(uint32_t host32bitvalue);
uinit16_t ntohs(uint16_t nett16bitvalue);
uinit32_t ntohl(uint32_t net32bitvalue);

3.2)內存操作函數

bxx和memxx的對比,特別是拷貝函數。我們用實驗要驗證bcopy和memcpy對重疊內存的處理。

#include <strings.h>
void bzero(void* dest,size_t nbytes);
void bcopy(const void* src ,void* dest,size_t nbytes);
int bcmp(const void *ptr1,const void *ptr2,size_t nbytes);

#include <string.h>
void* memset(void* dst, int c, size_t len);
void* memcpy(void* dst,const void* src,size_t nbytes);
int memcpy(const void* ptr1,const void* ptr2,size_t nbytes);

下麵我們通過測試代碼一看究竟,bcopy和memcpy的區別。
/*author:ZengJinlong ,xunlei*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>

int main()
{
    int a=0x01020304;
    printf("int size:%d\n",sizeof(int));

    char* p_a=&a;
    printf("a adress:%p,p_a:%x\n",&a,p_a);
    if(p_a==&a)
    {
        printf("equal\n");
    }

   printf("%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3]);

    if(p_a[0]==0x01&&p_a[1]==0x02)printf("Big-Endian\n");
    else printf("Little-Endian\n");

    printf("case 1\n");
    char* p1=p_a+2;
    printf("before bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);
    printf("bcopy 0 to 2 ,2 size\n");
    bcopy(p_a,p1,2);
    printf("0x%x\n",(int)*p1);
    printf("after bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);

    printf("case 2\n");
    a=0x01020304;
    p1=p_a+1;
    printf("before bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);
    printf("bcopy 0 to 1, 2 size\n");
    bcopy(p_a,p1,2);
    printf("0x%x\n",(int)*p1);
    printf("after bcopy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);

    printf("case 3\n");
    a=0x01020304;
    printf("before memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);
    printf("memcpy 0 to 2, 2 size\n");
    char* p2=p_a+2;
    memcpy(p2,p_a,2);
    printf("after memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);
    printf("0x%x\n",(int)*p2);

    printf("case 4\n");
    a=0x01020304;
    printf("before memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);
    printf("0x%x\n",a);
    p2=p_a+1;
    printf("memcpy 0 to 1 ,2 size\n");
    memcpy(p2,p_a,2);
    printf("after memcpy:%2x,%2x,%2x,%2x,%2x,%2x\n",p_a[0],p_a[1],p_a[2],p_a[3],p_a[4],p_a[5]);
    printf("0x%x\n",(int)*p2);

    return 0;
}

輸出結果為如圖所示,可以看出bcopy是可以正確處理內存重疊時候的拷貝,而memcpy則不能。想想,bcopy是如何實現的呢?memcpy很簡單,我們就不再多說。bcopy的代碼,我個人有個實現方案,很簡單。


圖 bcopy 和 memcpy的拷貝效果對比

bcopy實現原理,計算重疊了多少,然後先拷貝重疊部分,回過頭再拷貝前麵的部分。
實現代碼:
void bcopy_new(const void* src,void* dest,size_t nbytes)
{
    if(dest>src && ((unsigned)src+ nbytes > (unsigned)dest))
    {
        size_t gap=(unsigned int)dest - (unsigned int)src;
        size_t overlap = nbytes-gap;
        int i=0;
        while(i<overlap)
        {
            *((char*)dest+gap+i)=*((char*)dest+i);
            ++i;
        }
        i=0;
        while(gap--)*((char*)dest++)= *((char*)src++);
        return;
    }
    while(nbytes--)*((char*)dest++)= *((char*)src++);
    return;
}

將bcopy_new替換掉上麵bocpy的例子,可以得出和bcopy一樣的結果。
3.3)IP地址轉換函數
由點分格式轉換成網絡字節碼以及反過來。記住兩個即可。

#include <arpa/inet.h>
int inet_pton(int familyconst char* strptr,void* addrptr);
const char* inet_ntop(int family,const void* addrptr,char* strptr,size_t len);




最後更新:2017-04-03 05:39:32

  上一篇:go 關於T—SQL與SQL企業管理器
  下一篇:go centos 徹底刪除nodejs默認安裝文件