socket編程

什么是socket(套接字)

socket是一種計(jì)算機(jī)間約定好的傳輸方式(有點(diǎn)抽象)金抡。其本身為一串?dāng)?shù)字已球,unix將一切視為文件巍沙。每個(gè)文件都有自己的文件標(biāo)識(shí)符(一串?dāng)?shù)字)犯祠,那么網(wǎng)絡(luò)連接也是一個(gè)文件,他的文件標(biāo)識(shí)符就是socket窖铡。

  • IP

    • IPv4:一個(gè)32位整型數(shù)疗锐,4個(gè)字節(jié),表現(xiàn)為點(diǎn)分十進(jìn)制字符串费彼,eg:192.168.247.135

    • IPv6:一個(gè)128位整形滑臊,十六個(gè)字節(jié),分成八份箍铲。每份兩個(gè)字節(jié)雇卷,用十六進(jìn)制表示,0000-ffff。eg:2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b

    • 如何查看本機(jī)IP关划?

      打開CMD膘融,輸入ipconfig

  • 端口

    • 端口的作用是定位到主機(jī)上的某一個(gè)進(jìn)程祭玉。
    • 一個(gè)unsigned short氧映,十六位,0~65535

網(wǎng)絡(luò)分層模型OSI/ISO

分若干層脱货,長(zhǎng)這樣:

TCP岛都、UDP屬于傳輸層協(xié)議,IPv4振峻、IPv6屬于網(wǎng)絡(luò)層協(xié)議臼疫。

  • 我們只需處理應(yīng)用層數(shù)據(jù),并指定傳輸層扣孟、網(wǎng)絡(luò)層所用協(xié)議即可烫堤。

大小端及其轉(zhuǎn)換

  • 小端:主機(jī)字節(jié)序

    • 數(shù)據(jù)的低位字節(jié)存儲(chǔ)到內(nèi)存的低地址位 , 數(shù)據(jù)的高位字節(jié)存儲(chǔ)到內(nèi)存的高地址位。
    • 0x12345678 ---->> 78 56 34 12 內(nèi)存地址低—>高
  • 大端:網(wǎng)絡(luò)字節(jié)序

    • 據(jù)的低位字節(jié)存儲(chǔ)到內(nèi)存的高地址位 , 數(shù)據(jù)的高位字節(jié)存儲(chǔ)到內(nèi)存的低地址位凤价。
    • 0x12345678 ---->> 12 34 56 78 內(nèi)存地址低—>高
    // 有一個(gè)16進(jìn)制的數(shù), 有32位 (int): 0xab5c01ff
    // 字節(jié)序, 最小的單位: char 字節(jié), int 有4個(gè)字節(jié), 需要將其拆分為4份
    // 一個(gè)字節(jié) unsigned char, 最大值是 255(十進(jìn)制) ==> ff(16進(jìn)制) 
                     內(nèi)存低地址位                內(nèi)存的高地址位
    --------------------------------------------------------------------------->
    小端:         0xff        0x01        0x5c        0xab
    大端:         0xab        0x5c        0x01        0xff
    
  • 轉(zhuǎn)換:

    • 一般轉(zhuǎn)換

    一般pc機(jī)用小端方式儲(chǔ)存數(shù)據(jù)鸽斟,那么我們?cè)诎l(fā)送數(shù)據(jù)前,就要將數(shù)據(jù)從小端轉(zhuǎn)換為大端利诺。接收到數(shù)據(jù)后富蓄,又要從大端轉(zhuǎn)換為小端。

    #include <arpa/inet.h>
    // u:unsigned
    // 16: 16位, 32:32位
    // h: host, 主機(jī)字節(jié)序
    // n: net, 網(wǎng)絡(luò)字節(jié)序
    // s: short
    // l: int
    
    // 這套api主要用于 網(wǎng)絡(luò)通信過(guò)程中 IP 和 端口 的 轉(zhuǎn)換
    // 將一個(gè)短整形從主機(jī)字節(jié)序 -> 網(wǎng)絡(luò)字節(jié)序
    uint16_t htons(uint16_t hostshort);   
    // 將一個(gè)整形從主機(jī)字節(jié)序 -> 網(wǎng)絡(luò)字節(jié)序
    uint32_t htonl(uint32_t hostlong);    
    
    // 將一個(gè)短整形從網(wǎng)絡(luò)字節(jié)序 -> 主機(jī)字節(jié)序
    uint16_t ntohs(uint16_t netshort)
    // 將一個(gè)整形從網(wǎng)絡(luò)字節(jié)序 -> 主機(jī)字節(jié)序
    uint32_t ntohl(uint32_t netlong);
    
    • IP地址轉(zhuǎn)換

    IP地址雖為整形(int慢逾,4字節(jié))立倍,但實(shí)際以字符串來(lái)表示。以下函數(shù)可將字符串類型的IP地址進(jìn)行大小端轉(zhuǎn)換侣滩。

    // 主機(jī)字節(jié)序的IP地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
    // 主機(jī)字節(jié)序的IP地址是字符串, 網(wǎng)絡(luò)字節(jié)序IP地址是整形
    int inet_pton(int af, const char *src, void *dst); 
    

    af:

    AF_INET: ipv4 格式的 ip 地址口注,

    AF_INET6: ipv6 格式的 ip 地址

    src:

    要轉(zhuǎn)換的ip地址:102.168.0.1

    dst:

    一個(gè)指針,轉(zhuǎn)換得到的大端整形IP地址被放入這塊內(nèi)存君珠。

     #include <arpa/inet.h>
     // 將大端的整形數(shù), 轉(zhuǎn)換為小端的點(diǎn)分十進(jìn)制的IP地址        
     const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    

    與上相似寝志,size為dst內(nèi)存的大小。

TCP流程

Server:

  1. 建立用于監(jiān)聽的套接字:

    SOCKET socket(int af, int type, int protocol);
    

    af:AF_INET or AF_INET6葛躏,代表IPV4澈段、IPV6(PF一樣)

    type:SOCK_STREAM or SOCK_DGRAM,前者對(duì)應(yīng)TCP舰攒,后者對(duì)應(yīng)UDP

    protocol:IPPROTO_TCP or IPPTOTO_UDP,也可賦0悔醋,表示根據(jù)前兩個(gè)參數(shù)自行選取摩窃。

  1. 監(jiān)聽套接字本機(jī)的IP、端口綁定。(填寫自己的地址)

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

    參數(shù):

    sockfd:上一步得到的用于監(jiān)聽的描述符

    *addr:一個(gè)指向sockaddr類型結(jié)構(gòu)體的指針鹦聪,其中存儲(chǔ)著本地的IP地址以及端口。

    • sockaddr結(jié)構(gòu)體長(zhǎng)這樣:

      struct sockaddr{
          sa_family_t  sin_family;   //地址族(Address Family)蒂秘,也就是地址類型泽本,16個(gè)字節(jié),AF_INET
          char         sa_data[14];  //IP地址+端口號(hào),端口2字節(jié)姻僧,IP地址4字節(jié)规丽,空閑(0)8個(gè)字節(jié)
      };
      

      第二項(xiàng)將IP與端口揉在一起,不好填撇贺。我們用sockaddr_in填

    • sockaddr_in結(jié)構(gòu)體長(zhǎng)這樣:

      struct sockaddr_in{
          sa_family_t     sin_family;   //地址族(Address Family)赌莺,也就是地址類型
          uint16_t        sin_port;     //16位的端口號(hào)
          struct in_addr  sin_addr;     //32位IP地址
          char            sin_zero[8];  //不使用,一般用0填充
      };
      
      //其中松嘶,in_addr長(zhǎng)這樣:
      struct in_addr{
          in_addr_t  s_addr;  //32位的IP地址艘狭。in_addr_t 在頭文件 <netinet/in.h> 中定義,等價(jià)于 unsigned long
      };
      
  • 使用中翠订,先使用sockaddr_in初始化巢音,再將其轉(zhuǎn)換為sockaddr類型。如下例:

    //創(chuàng)建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //創(chuàng)建sockaddr_in結(jié)構(gòu)體變量
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每個(gè)字節(jié)都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    
    //將套接字和IP尽超、端口綁定
    connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    

addrlen:參數(shù)2所對(duì)應(yīng)結(jié)構(gòu)體內(nèi)存的大小港谊,由sizeof計(jì)算得出。

成功返回0橙弱,失敗返回-1歧寺。

  1. 設(shè)置監(jiān)聽,監(jiān)聽有無(wú)客戶端連接棘脐。

    int listen(SOCKET sock, int backlog);
    

    讓套接字進(jìn)入被動(dòng)監(jiān)聽狀態(tài)斜筐。

    backlog :為請(qǐng)求隊(duì)列的最大長(zhǎng)度。

    請(qǐng)求隊(duì)列:

    當(dāng)套接字正在處理客戶端請(qǐng)求時(shí)蛀缝,如果有新的請(qǐng)求進(jìn)來(lái)顷链,套接字是沒法處理的,只能把它放進(jìn)緩沖區(qū)屈梁,待當(dāng)前請(qǐng)求處理完畢后嗤练,再?gòu)木彌_區(qū)中讀取出來(lái)處理。如果不斷有新的請(qǐng)求進(jìn)來(lái)在讶,它們就按照先后順序在緩沖區(qū)中排隊(duì)煞抬,直到緩沖區(qū)滿。這個(gè)緩沖區(qū)构哺,就稱為請(qǐng)求隊(duì)列(Request Queue)革答。

    緩沖區(qū)的長(zhǎng)度(能存放多少個(gè)客戶端請(qǐng)求)可以通過(guò) listen() 函數(shù)的 backlog 參數(shù)指定战坤,但究竟為多少并沒有什么標(biāo)準(zhǔn),可以根據(jù)你的需求來(lái)定残拐,并發(fā)量小的話可以是10或者20途茫。

    如果將 backlog 的值設(shè)置為 SOMAXCONN,就由系統(tǒng)來(lái)決定請(qǐng)求隊(duì)列長(zhǎng)度溪食,這個(gè)值一般比較大囊卜,可能是幾百,或者更多错沃。

    當(dāng)請(qǐng)求隊(duì)列滿時(shí)栅组,就不再接收新的請(qǐng)求,對(duì)于 Linux捎废,客戶端會(huì)收到 ECONNREFUSED 錯(cuò)誤笑窜,對(duì)于 Windows,客戶端會(huì)收到 WSAECONNREFUSED 錯(cuò)誤登疗。

    注意:listen() 只是讓套接字處于監(jiān)聽狀態(tài)排截,并沒有接收請(qǐng)求。接收請(qǐng)求需要使用 accept() 函數(shù)辐益。

    成功返回0断傲,失敗返回-1

  1. 等待客戶端連接請(qǐng)求,不來(lái)就阻塞智政,來(lái)了就建立新的連接认罩,得到新的用于通信的套接字

    SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
    

    參數(shù)同bind。

    不同的是续捂,bind中sockaddr保存的是本機(jī)的IP與端口垦垂,而此函數(shù)中的sockaddr需要新建一個(gè)結(jié)構(gòu)體,不用初始化牙瓢,執(zhí)行函數(shù)時(shí)會(huì)自動(dòng)保存客戶端的IP地址與端口號(hào)劫拗。

    accept() 返回一個(gè)新的套接字來(lái)和客戶端通信,addr 保存了客戶端的IP地址和端口號(hào)矾克,而 sock 是服務(wù)器端的套接字页慷。后面和客戶端通信時(shí),要使用這個(gè)新生成的套接字胁附,而不是原來(lái)服務(wù)器端的套接字酒繁。

    最后需要說(shuō)明的是:listen() 只是讓套接字進(jìn)入監(jiān)聽狀態(tài),并沒有真正接收客戶端請(qǐng)求控妻,listen() 后面的代碼會(huì)繼續(xù)執(zhí)行州袒,直到遇到 accept()。accept() 會(huì)阻塞程序執(zhí)行(后面代碼不能被執(zhí)行)饼暑,直到有新的請(qǐng)求到來(lái)稳析。

  1. 收發(fā)文件

    int send(SOCKET sock, const char *buf, int len, int flags);
    

    sock:即用于通信的socket洗做,也就是accept等號(hào)左邊那玩意弓叛。

    buf:指向一塊內(nèi)存彰居。注意,const只讀撰筷,也就是說(shuō)我們只讀這塊內(nèi)存的數(shù)據(jù)陈惰,不用寫。*

    len:內(nèi)存的大小

    flags:特殊的屬性毕籽,一般不使用抬闯,指定為0

    返回:大于0-->實(shí)際發(fā)送的字節(jié)數(shù),等于0-->對(duì)方斷開連接关筒,-1-->接受失敗

int recv(SOCKET sock, char *buf, int len, int flags);

這里的*buf是可以寫的溶握。

  1. 關(guān)閉套接字

    close(SOCKET sock)

Client:

  1. 創(chuàng)建用于通信的套接字

    socket()

  2. 連接服務(wù)器,需要知道服務(wù)器的IP與端口

    connect()

    int connect(SOCKET sock, const struct sockaddr *addr, int addrlen); 
    

    參數(shù)與bind蒸播、accept一樣睡榆。

    其中,sockaddr存的是本機(jī)(Client)的IP與端口袍榆,所以與bind完全一致胀屿,要初始化。

  3. 收發(fā)文件

    send包雀、recv

  4. 關(guān)閉套接字

    close()

[圖片上傳失敗...(image-a27a45-1648644517809)]

程序?qū)嵗?/h2>

服務(wù)器端代碼 server.cpp

#include <stdio.h>
#include <winsock2.h>
#pragma comment (1lib, "ws2_32.lib")  //加載 ws2_32.dll
int main(){
    //初始化 DLL
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);
    
    //創(chuàng)建套接字
    //PF_INET:IPV4地址宿崭,SOCK_STREAM:面向聯(lián)接的傳輸方式(另一種SOCK_DGRAM),IPPROTO_TCP:使用TCP協(xié)議
    SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //綁定套接字
    sockaddr_in sockAddr; //sockaddr_in是包中定義好的結(jié)構(gòu)體
    memset(&sockAddr, 0, sizeof(sockAddr));  //每個(gè)字節(jié)都用0填充才写,void* memset(起始指針,填充數(shù)值,內(nèi)存長(zhǎng)度)葡兑,給指定內(nèi)存賦值
    sockAddr.sin_family = PF_INET;  //使用IPv4地址
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    sockAddr.sin_port = htons(1234);  //端口
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //將套接字servSock與特定IP地址與端口綁定。
    
    //進(jìn)入監(jiān)聽狀態(tài)赞草,讓套接字處于被動(dòng)監(jiān)聽狀態(tài)讹堤。所謂被動(dòng)監(jiān)聽,是指套接字一直處于“睡眠”中房资,直到客戶端發(fā)起請(qǐng)求才會(huì)被“喚醒”
    listen(servSock, 20);
    
    //接收客戶端請(qǐng)求
    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize); //accept() 函數(shù)用來(lái)接收客戶端的請(qǐng)求蜕劝。程序一旦執(zhí)行到 accept() 就會(huì)被阻塞(暫停運(yùn)行),直到客戶端發(fā)起請(qǐng)求轰异。
    
    //向客戶端發(fā)送數(shù)據(jù)
    char *str = "Hello World!";
    send(clntSock, str, strlen(str)+sizeof(char), NULL);
    
    //關(guān)閉套接字
    closesocket(clntSock);
    closesocket(servSock);
    
    //終止 DLL 的使用
    WSACleanup();
    
    return 0;
}

客戶端代碼 client.cpp

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")  //加載 ws2_32.dll
int main(){
    //初始化DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    
    //創(chuàng)建套接字
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //向服務(wù)器發(fā)起請(qǐng)求
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每個(gè)字節(jié)都用0填充
    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(1234);
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); // connect() 向服務(wù)器發(fā)起請(qǐng)求岖沛,直到服務(wù)器傳回?cái)?shù)據(jù)后,connect() 才運(yùn)行結(jié)束搭独。
    
    //接收服務(wù)器傳回的數(shù)據(jù)
    char szBuffer[MAXBYTE] = {0};
    recv(sock, szBuffer, MAXBYTE, NULL);
    
    //輸出接收到的數(shù)據(jù)
    printf("Message form server: %s\n", szBuffer);
    
    //關(guān)閉套接字
    closesocket(sock);
    
    //終止使用 DLL
    WSACleanup();
    system("pause");
    
    return 0;
}

WSAStartup()

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

初始化婴削。

wVersionRequested 為 WinSock 規(guī)范的版本號(hào),低字節(jié)為主版本號(hào)牙肝,高字節(jié)為副版本號(hào)(修正版本號(hào))唉俗;WinSock 規(guī)范的最新版本號(hào)為 2.2嗤朴。

lpWSAData 為指向 WSAData 結(jié)構(gòu)體的指針。

具體實(shí)例:

WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); //2.2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虫溜,一起剝皮案震驚了整個(gè)濱河市雹姊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衡楞,老刑警劉巖吱雏,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瘾境,居然都是意外死亡歧杏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門迷守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)犬绒,“玉大人,你說(shuō)我怎么就攤上這事兑凿】Γ” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵急膀,是天一觀的道長(zhǎng)沮协。 經(jīng)常有香客問我,道長(zhǎng)卓嫂,這世上最難降的妖魔是什么慷暂? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮晨雳,結(jié)果婚禮上行瑞,老公的妹妹穿的比我還像新娘。我一直安慰自己餐禁,他們只是感情好血久,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帮非,像睡著了一般氧吐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上末盔,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天筑舅,我揣著相機(jī)與錄音,去河邊找鬼陨舱。 笑死翠拣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的游盲。 我是一名探鬼主播误墓,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛮粮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谜慌?” 一聲冷哼從身側(cè)響起然想,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎畦娄,沒想到半個(gè)月后又沾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弊仪,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熙卡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了励饵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驳癌。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖役听,靈堂內(nèi)的尸體忽然破棺而出颓鲜,到底是詐尸還是另有隱情,我是刑警寧澤典予,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布甜滨,位于F島的核電站,受9級(jí)特大地震影響瘤袖,放射性物質(zhì)發(fā)生泄漏衣摩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一捂敌、第九天 我趴在偏房一處隱蔽的房頂上張望艾扮。 院中可真熱鬧,春花似錦占婉、人聲如沸泡嘴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酌予。三九已至,卻和暖如春奖慌,著一層夾襖步出監(jiān)牢的瞬間抛虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工升薯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莱褒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓涎劈,卻偏偏與公主長(zhǎng)得像广凸,于是被迫代替她去往敵國(guó)和親阅茶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容