套接字基礎
套接字類型
套接字存在于特定的通信協(xié)議(地址族)中幸海,只有類屬于同一地址族的套接字才能建立通信劫哼,套接字支持多種通信協(xié)議:
AF_LOCAL:Unix 系統(tǒng)本地通信
AF_INET:IP版本4
AF_INET6:IP版本6
Linux支持多種套接字類型,套接字類型:是指創(chuàng)建套接字的應用程序所希望的通信服務類型班眯。
SOCKET_STREAM:雙向可靠數(shù)據流,流式套接字,對應TCP
SOCKET_DGRAM:雙向不可靠數(shù)據報蓖柔,數(shù)據包套接字,對應UDP
SOCKET_RAW:是低于傳輸層的低級協(xié)議或物理網絡直接訪問风纠,可以訪問內部網絡接口况鸣。原始套接字,例如接收和發(fā)送ICMP報竹观。
套接字地址結構(IPv4)
struct sockaddr_in{
? unsigned short int sin_len;? /* IPv4地址長度 */
? short int sin_family; /* 地址類型 */
? unsigned short int sin_port; /* 存儲端口號 */
? struct in_addr sin_addr;? /*存儲IP地址 */
? unsigned char sin_zero[8];? /* 空字節(jié) */
}镐捧;
sin_family指代協(xié)議族,在TCP套接字編程中只能是AF_INET;
sin_zero是為了讓sockaddr與sockaddr_in兩個數(shù)據結構保持大小相同而保留的空字節(jié)臭增。
sin_port存儲端口號(使用網絡字節(jié)順序)懂酱,數(shù)據類型是一個16位的無符號整數(shù)類型;
sin_addr存儲IP地址誊抛,IP地址使用in_addr這個數(shù)據結構:
? struct in_addr{? unsigned long s_addr;? };
這個數(shù)據結構是由于歷史原因保留下來列牺,主要用作與以前的格式兼容。這里的s_addr按照網絡字節(jié)順序存儲IP地址拗窃。
設置地址信息的實例(IPv4)
struct sockaddr_in mysock; /*設置sockaddr_in的結構體變量mysock */
mysock.sin_family=AF_INET; /*地址族*/
mysock.sin_port=htons(3490); /*short,NBO*/
mysock.sin_addr.s_addr=inet_addr(“192.168.1.221”); /*設置地址為192.168.1.221*/
bzero(&(mysock.sin_zero),8); /*設置sin_zero為8位保留字節(jié)*/
注意:如果mysock.sin_addr.s_addr=INADDR_ANY,則不指定IP地址(用于server程序)瞎领。
字節(jié)排序函數(shù)
網絡中存在多種類型的機器,這些不同類型的機器表示數(shù)據的字節(jié)順序是不同的随夸。網絡協(xié)議中的數(shù)據采用統(tǒng)一的網絡字節(jié)順序九默,因為只有采用統(tǒng)一的字節(jié)順序,才能在不同類型的硬件設備之間正確的發(fā)送和接收數(shù)據逃魄。廣域網規(guī)定的網絡字節(jié)順序采用大端字節(jié)順序方式荤西。
系統(tǒng)提供4個函數(shù)來進行字節(jié)順序轉換:
#include “netinet/in.h”
unsigned short int htons(unsigned short int hostshort);
unsigned long int htonl(unsigned long int hostlong);
unsigned short int ntons(unsigned short int netshort);
unsigned long int ntonl(unsigned long int netlong);
h:主機? n:網絡? s:短整數(shù)? l:長整數(shù)
其中。前兩個函數(shù)將主機字節(jié)順序轉換成網絡字節(jié)順序;后兩個函數(shù)將網絡字節(jié)順序轉換成主機字節(jié)順序邪锌。
在使用這些函數(shù)時勉躺,我們不關心主機或網絡順序的真實值到底是大端還是小端,只需要調用適當?shù)暮瘮?shù)來對給定值(函數(shù)的整型參數(shù))進行主機字節(jié)順序和網絡字節(jié)順序的轉換觅丰,它們的返回值就是經過轉換以后的結果饵溅。
字節(jié)操縱函數(shù)
系統(tǒng)提供兩組函數(shù)來處理多字節(jié)數(shù)據,一組函數(shù)是以b(byte)開頭妇萄,和BSD系統(tǒng)兼容的函數(shù)蜕企;另一組是以mem開頭,ANSI C所提供的函數(shù)冠句。
#include <string.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int? bcmp(const void *src, void *dest, size_t nbytes); /*返回0則相同轻掩,非0不相同*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 上述三個函數(shù)源自BSD
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int? ? memcmp(const void *ptr1, const void *ptr2, size_t nbytes)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?上述三個函數(shù)屬于ANSI C
memcpy函數(shù)等同于bcopy,差別是bcopy可以處理源src和目標dest相重疊的情況懦底,而memcpy則對這種情況沒有定義唇牧。
memset函數(shù)將參數(shù)s指定的內存區(qū)域的前n個字節(jié)設置為參數(shù)c的內容;
bcmp比較任意兩個內存區(qū)域聚唐,即s1指定的內存區(qū)域與s2指定的內存區(qū)域的前n個字節(jié)丐重,若相同則返回值為0签钩,否則返回值為非0
bzero函數(shù)將目標中指定數(shù)目的字節(jié)置為0弧关,這個函數(shù)經常用來把套接字地址結構初始化為0蒜田,如:
bzero(&servaddr,sizeof(servaddr));
bcopy將指定數(shù)目的字節(jié)從源src移動到目標dest指定的內存區(qū)域荒椭;
地址轉換函數(shù)
地址轉換函數(shù)負責在ASCII字符串和網絡字節(jié)順序的二進制值之間進行地址轉換蒋得。
inet_aton,inet_addr和inet_ntoa函數(shù)
#include <arpa/inet.h>
int inet_aton(const char *strptr,struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
inet_aton函數(shù)將strptr所指向的字符串轉換成32位的網絡字節(jié)序二進制值忆嗜,并存儲在指針addrptr指向的in_addr結構體中州胳,若成功衷笋,返回1客峭。
inet_addr函數(shù)纳猪,其轉換結果作為返回值返回32位二進制網絡字節(jié)序地址,若轉換錯桃笙,則返回INADDR_NONE氏堤。
inet_addr進行相同的轉換,但不進行有效性驗證搏明,當IP地址是255.255.255.255時鼠锈,會認為這是個無效的IP地址,但對于目前大部分的路由器上星著,這個IP都是有效的购笆。
inet_aton函數(shù)將tcp所指的字符串(點分十進制數(shù)串,如192.168.0.1)轉換成32位的網絡字節(jié)序二進制虚循,并通過指針addrptr來存儲同欠。這個函數(shù)需要對字符串所指的地址進行有效性驗證样傍。但如果strptr為空,函數(shù)仍然成功铺遂,但不存儲任何結果衫哥。
char *inet_ntoa(struct in_addr inaddr);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 返回:指向點分十進制數(shù)串的指針
函數(shù)inet_ntoa將32位的網絡字節(jié)序二進制IPv4地址轉換成相應的點分十進制數(shù)串。但由于返回值所指向的串留在靜態(tài)內存中襟锐,這意味著函數(shù)是不可重入的撤逢。
需要注意的是這個函數(shù)是以結構為參數(shù)的,而不是指針粮坞。
上述三個地址轉換函數(shù)都只能處理IPv4協(xié)議蚊荣,而不能處理IPv6地址。
Tcp套接字
TCP套接字實現(xiàn)過程
服務器端步驟
1.創(chuàng)建套接字
2..綁定套接字
3..設置套接字為監(jiān)聽模式莫杈,進入被動接受連接請求狀態(tài)
4..接受請求互例,建立連接
5.讀/寫數(shù)據
6.終止連接
客戶端步驟
1.創(chuàng)建套接字
2.與遠程服務程序連接
3.讀/寫數(shù)據
5.終止連接
基本套接字函數(shù) - socket
socket實質上提供了進程通信的端點。進程通信之前筝闹,雙方首先必須各自創(chuàng)建一個端點敲霍,否則是沒有辦法建立聯(lián)系并相互通信的。正如打電話之前丁存,雙方必須各自擁有一臺電話機一樣。在網間網內部柴我,每一個socket用一個半相關描述:
(協(xié)議解寝,本地地址,本地端口)
一個完整的socket有一個本地唯一的socket號艘儒,由操作系統(tǒng)分配聋伦。
對于基于TCP的通信,無論是服務器還是客戶界睁,都必須首先產生其TCP通信傳輸端點觉增,即TCP套接字。
應用程序通過調用socket()產生套接字翻斟。該函數(shù)調用必須給出所使用的地址簇逾礁、套接字類型和協(xié)議標志。該函數(shù)返回一個套接字描述符访惜。
由于系統(tǒng)中套接字也是一種文件嘹履,所以套接字描述符可以看成是一種文件描述符。之后的任何I/O操作都是作用于該套接字描述符债热。其數(shù)據結構包括一個網絡連接的5種信息:通信協(xié)議砾嫉、本地協(xié)議地址、本機主機端口窒篱、遠程主機地址和遠程協(xié)議端口焕刮。
socket函數(shù):為了執(zhí)行網絡輸入輸出舶沿,一個進程必須做的第一件事就是調用socket函數(shù)獲得一個文件描述符。
基本套接字函數(shù)-bind
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_len len)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 返回:0-成功;-1-出錯并置errno
該函數(shù)指明套接字將使用本地的哪一個協(xié)議端口進行數(shù)據傳送(IP地址和端口號)恕沫,注意:協(xié)議地址addr是通用地址监憎。
Len是該地址結構(第二個參數(shù))的長度。
一般而言婶溯,服務器調用此函數(shù)鲸阔,而客戶則很少調用它。
因為:客戶端是主動向服務器發(fā)出請求的迄委,客戶開始發(fā)送數(shù)據褐筛,系統(tǒng)就給客戶端分配一個隨機的端口,這個端口和客戶端的IP會隨著數(shù)據一起發(fā)給服務器叙身,服務器就可以從中或得客戶的IP和端口渔扎,接下來服務器就可以利用獲得的IP和端口給客戶端回應消息。
bind函數(shù)用法
setsockopt函數(shù)?
該函數(shù)用于任意類型、任意狀態(tài)套接口的設置選項值榨了,盡管在不同協(xié)議層上存在選項煎谍,但本函數(shù)定義了最高的“套接口”層次上得選項。選項影響套接口的操作龙屉,諸如:廣播數(shù)據是否可以從套接口發(fā)送等等呐粘。
基本套接字函數(shù)-listen
listen函數(shù):listen函數(shù)僅被TCP服務器調用,它的作用是將用sock創(chuàng)建的主動套接口轉換成被動套接口转捕,并等待來自客戶端的連接請求作岖。
基本套接字函數(shù)-close
#include <unistd.h>
int close(int sockfd);
? ? ? ? ? ? ? ? ? 返回:0-OK矾瑰;-1-出錯;
close函數(shù)缺省功能是將套接字做上“已關閉”標記隘擎,并立即返回到進程殴穴。這個套接字不能再為該進程所用。
正常情況下嵌屎,close將引發(fā)向TCP的四分節(jié)終止序列,但在終止前將發(fā)送已排隊的數(shù)據恍涂;
如果套接字描述符訪問計數(shù)在調用close后大于0(在多個進程共享同一個套接字的情況下)宝惰,則不會引發(fā)TCP終止序列(即不會發(fā)送FIN分節(jié));
基本套接字函數(shù)-shutdown
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
? ? ? ? ? ? ? ? ? 返回:0-OK再沧;-1-出錯尼夺,并置相應的errno的值;
該函數(shù)立即發(fā)送FIN分節(jié)(無論其訪問計數(shù)是否大于0)炒瘸。shutdown根據參數(shù)howto關閉指定方向的數(shù)據傳輸淤堵;
SHUT_RD:關閉連接的讀這一半,不再接收套接字中的數(shù)據且現(xiàn)留在接收緩沖區(qū)的數(shù)據作廢顷扩;
SHUT_WR :關閉連接的寫這一半(半關閉)拐邪,當留在套接字發(fā)送緩沖區(qū)中的數(shù)據都被發(fā)送,后跟tcp連接終止序列隘截,不管訪問計數(shù)是否大于0扎阶;此后將不能在執(zhí)行對套接字的任何寫操作汹胃;
SHUT_RDWR:連接的讀、寫都關閉东臀,這等效于調用shutdown兩次着饥,一次調用是用SHUT_RD,第二次用SHUT_WR惰赋。
基本套接字函數(shù)-read
#include <unistd.h>
int read(int fd, char *buf, int len);
返回:大于0-讀寫字節(jié)大性椎簟;-1-出錯赁濒;
調用函數(shù)read時轨奄,有如下幾種情況:
套接字接收緩沖區(qū)接收數(shù)據,返回接收到的字節(jié)數(shù)流部;
tcp協(xié)議收到FIN數(shù)據戚绕,返回0;
tcp協(xié)議收到RST數(shù)據枝冀,返回-1舞丛,同時errno為ECONNRESET;
進程阻塞過程中接收到信號果漾,返回-1球切,同時errno為EINTR。
read(connfd绒障,buff吨凑,strlen(buff));
基本套接字函數(shù)-write
#include <unistd.h>
int write(int fd, char *buf, int len);
? ? ? ? ? ? ? ? ? ? 返回:大于0-讀寫字節(jié)大小户辱;-1-出錯鸵钝;
調用函數(shù)write,有如下幾種情況:
套接字發(fā)送緩沖區(qū)有足夠空間庐镐,返回發(fā)送的字節(jié)數(shù)恩商;
tcp協(xié)議接收到RST數(shù)據,返回-1必逆,同時errno為ECONNRESET怠堪; ;
進程阻塞過程中接收到信號名眉,返回-1粟矿,同時errno為EINTR。
write(connfd损拢,buff陌粹,strlen(buff));
數(shù)據傳輸函數(shù)-send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send (int fd, const void *msg, size_t len, int flags);
? ? ? 返回:非0-發(fā)送成功的數(shù)據長度;-1-出錯福压;
flags 是傳輸控制標志申屹,其值定義如下:
0:常規(guī)操作绘证,如同write()函數(shù)
MSG_OOB,發(fā)送帶外數(shù)據(TCP緊急數(shù)據)哗讥。
MSG_DONTROUTE:忽略底層協(xié)議的路由設置嚷那,只能將數(shù)據發(fā)送給與發(fā)送機處在同一個網絡中的機器上。
數(shù)據傳輸函數(shù)-recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int fd, void *buf ,size_t len, int flags);
? ? ? ? ? ? 返回:大于0表示成功接收的數(shù)據長度杆煞;0: 對方已關閉魏宽,-1:出錯。
flags是傳輸控制標志决乎,其值定義如下:
0:常規(guī)操作队询,如同read()函數(shù);
MSG_PEEK:只查看數(shù)據而不讀出數(shù)據构诚,后續(xù)讀操作仍然能讀出所查看的該數(shù)據蚌斩;
MSG_OOB:忽略常規(guī)數(shù)據,而只讀帶外數(shù)據范嘱;
MSG_WAITALL:recv函數(shù)只有在將接收緩沖區(qū)填滿后才返回送膳。
域名解析函數(shù)-gethostbyname
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname)
? ? 返回:非空指針-成功;空指針-出錯丑蛤,同時設置h_error
該函數(shù)既可解析IPv4地址叠聋,也可解析IPv6地址;
該函數(shù)既可接收域名受裹,也可接收點分十進制參數(shù)
當hostname為點分十進制時碌补,函數(shù)并不執(zhí)行網絡查詢,而是直接將其拷貝到結果字段中棉饶。
此函數(shù)返回的非空指針指向下面的hostent結構
TCP服務器模板
TCP客戶模板
UDP套接字
實現(xiàn)UDP套接字基本步驟分為服務器端和客戶端兩部分:
服務器端
1.建立UDP套接字厦章;
2.綁定套接字到特定地址;
3.等待并接收客戶端信息照藻;
4.處理客戶端請求袜啃;
5.發(fā)送信息回客戶端;
6.關閉套接字岩梳;
客戶端步驟
1.建立UDP套接字囊骤;
2.發(fā)送信息給服務器晃择;
3.接收來自服務器的信息冀值;
4.關閉套接字
UDP數(shù)據傳輸函數(shù)-sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen);
? ? ? ? 返回:大于0-成功發(fā)送數(shù)據長度;-1-出錯宫屠;
UDP套接字使用無連接協(xié)議列疗,因此必須使用sendto函數(shù),指明目的地址浪蹂;
flags是傳輸控制標志抵栈,其值定義如下:
0:常規(guī)操作告材,如同write()函數(shù);
MSG_OOB:發(fā)送帶外數(shù)據古劲;
MSG_DONTROUTE:忽略底層路由協(xié)議斥赋,直接發(fā)送。
UDP數(shù)據傳輸函數(shù)-recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);
? ? ? ? ? 返回:大于0-成功接收數(shù)據長度产艾;-1-出錯疤剑;
UDP套接字使用無連接協(xié)議,因此必須使用recvfrom函數(shù)闷堡,指明源地址隘膘;
flags是傳輸控制標志,其值定義如下:
0:常規(guī)操作杠览,如同read()函數(shù)弯菊;
MSG_PEEK:只察看數(shù)據而不讀出數(shù)據;
MSG_OOB:忽略常規(guī)數(shù)據踱阿,而只讀取帶外數(shù)據管钳;
from 和 fromlen 是“值-結果”參數(shù)。