Socket網(wǎng)絡(luò)編程指導(dǎo)
1/37
guoxiaol@mail.ustc.edu.cn
什么是Socket?
2/37
BSD Socket(伯克立套接字)是通過(guò)標(biāo)準(zhǔn)的UNIX文件描述符和其它程序通訊的一個(gè)方法,目前已經(jīng)被廣泛移植到各個(gè)平臺(tái)。
Socket是獨(dú)立于具體協(xié)議的網(wǎng)絡(luò)編程接口。在ISO模型中,主要位于會(huì)話層和傳輸層。
Socket的類型
3/37
流式套接字(SOCK_STREAM)
提供了一個(gè)面向連接,可靠的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)無(wú)差錯(cuò),無(wú)重復(fù)的發(fā)送且按發(fā)送順序接收。內(nèi)設(shè)流量控制,避免數(shù)據(jù)流超限;數(shù)據(jù)被看作是字節(jié)流,無(wú)長(zhǎng)度限制。
數(shù)據(jù)報(bào)式套接字(SOCK_DGRAM)
提供了一個(gè)無(wú)連接服務(wù)。數(shù)據(jù)包以獨(dú)立包形式被發(fā)送,不提供無(wú)差錯(cuò)保證,數(shù)據(jù)可能丟失或重復(fù),并且接收順序無(wú)序。
原始式套接字(SOCK_RAW)
該接口允許對(duì)較低層次協(xié)議,如IP、ICMP直接訪問(wèn)。
4/37
Socket所在層次示意圖
Application program
Stream
Socket
Interface
TCP
UDP
Datagram
Socket
Interface
Raw
Socket
Interface
IP
Physical and data link layers
基本套接字調(diào)用
5/37
創(chuàng)建套接字 socket();
綁定本機(jī)端口 bind();
建立連接 connect();
接受連接 accept();
監(jiān)聽(tīng)端口 listen();
數(shù)據(jù)傳輸 send(), recv() 等;
關(guān)閉套接字 close();
Socket相關(guān)的數(shù)據(jù)結(jié)構(gòu)
6/37
struct sockaddr_in
{
short int sin_family; /* 通信類型 */
unsigned short int sin_port; /* 端口號(hào),網(wǎng)絡(luò)字節(jié)順序*/
struct in_addr sin_addr; /* Internet 地址,網(wǎng)絡(luò)字節(jié)順序*/
unsigned char sin_zero[8]; /*沒(méi)用*/
};
struct in_addr
{
in_addr_t s_addr; /* 存儲(chǔ)32bit 的IP地址*/
}
網(wǎng)絡(luò)字節(jié)順序和主機(jī)字節(jié)順序
7/37
Big-Endian Byte Order:字節(jié)的高位在內(nèi)存中放在存儲(chǔ)單元的起始位置
00001010
00010111
00001110
00000110
00001010
00010111
00001110
00000110
Memory
Little-Endian Byte Order : 與Big-Endian相反
A
A+1
A+2
A+3
8/40
Host byte order( Little-Endian )
16-bit
32-bit
Network byte order(Big-Endian)
16-bit
32-bit
htons()
ntohs()
htonl()
ntohl()
網(wǎng)絡(luò)字節(jié)順序和主機(jī)字節(jié)順序的轉(zhuǎn)換
IP地址的轉(zhuǎn)換
9/37
int inet_aton(const char* strptr, struct in_addr *addrptr);
從點(diǎn)狀十進(jìn)制到32位2進(jìn)制的轉(zhuǎn)換,如“202.38.64.185” 到
11001010,00100110,01000000,10111001
char *inet_ntoa(struct in_addr inadd);
與inet_aton()的功能相反
相關(guān)的內(nèi)存操作函數(shù)
10/37
void *memset(void *buffer, int c, int count);
把buffer所指內(nèi)存區(qū)域的前count個(gè)字節(jié)設(shè)置成字符c。
void *memcpy(void *dest, void *src, unsigned int count);
由src所指內(nèi)存區(qū)域復(fù)制count個(gè)字節(jié)到dest所指內(nèi)存區(qū)域。
Void bzero(void *s, int n );
置字節(jié)字符串s的前n個(gè)字節(jié)為零。
域名和IP地址的轉(zhuǎn)換
11/37
struct hostent *gethostbyname(const char *name);
struct hostent
{
char *h_name; /* 主機(jī)的官方域名 */
char **h_aliases; /* 一個(gè)以NULL結(jié)尾的主機(jī)別名數(shù)組 */
int h_addrtype; /* 返回的地址類型,在Internet環(huán)境下為AF- INET */
int h_length; /* 地址的字節(jié)長(zhǎng)度 */
char **h_addr_list; /* 一個(gè)以0結(jié)尾的數(shù)組,包含該主機(jī)的所有地 址*/
};
#define h_addr h_addr_list[0] /*在h-addr-list中的第一個(gè)地址*/
建立Socket
12/37
int socket(int domain, int type, int protocol);
參數(shù)說(shuō)明:
domain:通信使用的協(xié)議族,即網(wǎng)絡(luò)的類型,對(duì)于 TCP/IP來(lái)說(shuō),是AF_INET
type: SOCK_STREAM / SOCK_DGRAM
protocol: 通常為0
返回整形的socket描述符,如果出錯(cuò),返回-1
Socket的配置
13/37
Socket描述符是一個(gè)指向內(nèi)部數(shù)據(jù)結(jié)構(gòu)的指針,它指向描述符表入口。調(diào)用Socket()函數(shù)時(shí),將建立一個(gè)Socket,為一個(gè)Socket數(shù)據(jù)結(jié)構(gòu)分配存儲(chǔ)空間。
兩個(gè)網(wǎng)絡(luò)程序之間的一個(gè)網(wǎng)絡(luò)連接包括五種信息:通信協(xié)議、本地主機(jī)地址和端口、遠(yuǎn)端主機(jī)地址和端口。
在使用socket進(jìn)行網(wǎng)絡(luò)傳輸以前,必須配置該socket。 面向連接的socket客戶端調(diào)用connect()函數(shù)在socket數(shù)據(jù)結(jié)構(gòu)中保存本地和遠(yuǎn)端信息。
無(wú)連接socket的客戶端和服務(wù)端以及面向連接socket的服務(wù)端通過(guò)調(diào)用bind()函數(shù)來(lái)配置本地信息。
綁定Socket
14/37
int bind(int sockfd,struct sockaddr_in *my_addr, int addrlen);
sockfd是socket()返回的socket描述符;
my_addr是指向包含本機(jī)IP地址及端口號(hào)等信息的 sockaddr類型的指針; addrlen一般被設(shè)置為sizeof(struct sockaddr_in)
成功被調(diào)用時(shí)返回0;出現(xiàn)錯(cuò)誤時(shí)返回"-1"
綁定前sockaddr_in的初始化
15/37
my_addr.sin_family = AF_INET; //選擇網(wǎng)絡(luò)類型為TCP/IP
my_addr.sin_addr.s_addr = inet_addr(“210.45.64.222");
my_addr.sin_port = htons( 8888 ); //選擇端口8888
addr_len = sizeof(struct sockaddr_in);
memset(&my_addr.sin_zero, ‘\0’, sizeof(my_addr.sin_zero));
建立連接(客戶端)
16/37
面向連接的客戶程序使用connect函數(shù)來(lái)配置socket并與遠(yuǎn)端服務(wù)器建立一個(gè)TCP連接,其函數(shù)原型為:
int connect( int sockfd, struct sockaddr_in *serv_addr,int addrlen);
serv_addr是包含遠(yuǎn)端主機(jī)IP地址和端口號(hào)的指針;addrlen是遠(yuǎn)端地址結(jié)構(gòu)的長(zhǎng)度
成功則返回0,出現(xiàn)錯(cuò)誤時(shí)返回-1
建立連接(服務(wù)器端)
17/37
服務(wù)器監(jiān)聽(tīng)端口:listen函數(shù)使socket處于被動(dòng)的監(jiān)聽(tīng)模式,并為該socket建立一個(gè)輸入數(shù)據(jù)隊(duì)列,將到達(dá)的服務(wù)請(qǐng)求保存在此隊(duì)列中,直到程序處理它們。
int listen(int sockfd, int backlog);
backlog:請(qǐng)求連接隊(duì)列的最大長(zhǎng)度
成功返回0,出錯(cuò)返回-1
建立連接(服務(wù)器端)
18/37
accept()函數(shù)讓服務(wù)器接收客戶的連接請(qǐng)求。在建立好輸入隊(duì)列后,服務(wù)器就調(diào)用accept函數(shù),然后睡眠并等待客戶的連接請(qǐng)求。
int accept(int sockfd, sockaddr_in *addr, int *addrlen);
addr是指向sockaddr_in變量的指針,該變量存放提出連接請(qǐng)求服務(wù)的主機(jī)的信息
返回新的socket描述符,和請(qǐng)求連接進(jìn)程的地址聯(lián)系起來(lái)在新的socket描述符上進(jìn)行數(shù)據(jù)傳輸操作。原來(lái)的socket繼續(xù)listen
數(shù)據(jù)傳輸(1)
19/37
send()和recv()這兩個(gè)函數(shù)用于面向連接的socket上進(jìn)行數(shù)據(jù)傳輸。
send()函數(shù)原型為:
int send(int sockfd, const void *msg, int len, int flags);
sockfd是用來(lái)傳輸數(shù)據(jù)的socket描述符;msg是指向要發(fā)送數(shù)據(jù)的指針;len是以字節(jié)為單位的數(shù)據(jù)長(zhǎng)度;flags一般置為0
send() 返回實(shí)際發(fā)送的字節(jié)數(shù),可能會(huì)少于希望發(fā)送的數(shù)據(jù)。在程序中應(yīng)該將send()的返回值與欲發(fā)送的字節(jié)數(shù)進(jìn)行比較。當(dāng)返回值與len不匹配時(shí),應(yīng)該進(jìn)行處理。
數(shù)據(jù)傳輸(2)
20/37
recv()函數(shù)原型為:
int recv(int sockfd, void *buf, int len, unsigned int flags);
buf 是存放接收數(shù)據(jù)的緩沖區(qū);len是緩沖區(qū)的長(zhǎng)度。flags也被置為0。
recv()返回實(shí)際接收的字節(jié)數(shù),當(dāng)出現(xiàn)錯(cuò)誤時(shí),返回-1
數(shù)據(jù)傳輸(3)
21/37
sendto()和recvfrom()用于在無(wú)連接的數(shù)據(jù)報(bào)socket方
式下進(jìn)行數(shù)據(jù)傳輸。由于本地socket沒(méi)有與遠(yuǎn)端機(jī)器建立連接,所以在發(fā)送數(shù)據(jù)時(shí)要指明目的地址。
sendto()函數(shù)原型為:
int sendto(int sockfd, const void *buf,int buflen, unsigned int flags, const struct sockaddr_in *to, int tolen);
數(shù)據(jù)傳輸(4)
22/37
recvfrom()函數(shù)原型為:
int recvfrom(int sockfd,void *buf,int buflen, unsigned int flags,struct sockaddr_in *from,int *fromlen);
recvfrom()函數(shù)返回接收到的字節(jié)數(shù),當(dāng)出錯(cuò)時(shí)返回-1
結(jié)束傳輸
23/37
close()函數(shù)用于釋放socket,停止在該socket上的任何數(shù)據(jù)操作: close(sockfd);
也可以調(diào)用shutdown() 來(lái)關(guān)閉該socket
該函數(shù)允許只停止某個(gè)方向上的數(shù)據(jù)傳輸,而一個(gè)方向上的數(shù)據(jù)傳輸繼續(xù)進(jìn)行。
int shutdown(int sockfd,int how);
參數(shù) how允許為shutdown操作選擇以下幾種方式:
0-------不允許繼續(xù)接收數(shù)據(jù)
1-------不允許繼續(xù)發(fā)送數(shù)據(jù)
2-------不允許繼續(xù)發(fā)送和接收數(shù)據(jù),
shutdown在操作成功時(shí)返回0,出錯(cuò)時(shí)返回-1。
C/S結(jié)構(gòu)
24/37
服務(wù)器端要先啟動(dòng),提供相應(yīng)服務(wù):
1:打開(kāi)一通信通道并告知本地主機(jī),它愿意在某一個(gè)公認(rèn)地址上接收客戶請(qǐng)求。
2:等待客戶請(qǐng)求到達(dá)該端口。
3:接收到服務(wù)請(qǐng)求,處理該請(qǐng)求并發(fā)送應(yīng)答信號(hào)。
4:返回第二步,等待另一客戶請(qǐng)求
5:關(guān)閉服務(wù)器。
客戶端:
1、打開(kāi)一通信通道,并連接到服務(wù)器所在主機(jī)的特定端口。
2、向服務(wù)器發(fā)送服務(wù)請(qǐng)求報(bào)文,等待并接收應(yīng)答;繼續(xù)提出請(qǐng)求……
3、請(qǐng)求結(jié)束后關(guān)閉通信通道并終止。
流程圖
25/37
TCP服務(wù)器端
(循環(huán)服務(wù)器)
TCP客戶端
socket( )
bind( )
listen( )
accept( )
socket( )
send( )
connect( )
recv( )
recv( )
send( )
close( )
close( )
UDP服務(wù)器端
UDP客戶端
socket( )
bind( )
listen( )
recvfrom( )
sendto( )
socket( )
bind( )
close( )
close( )
簡(jiǎn)單的例子
26/37
int sockfd, newsockfd,addr_len, sendnum;
struct sockaddr_in my_addr, their_addr;
char * msg = “welcome”;
sockfd = socket( AF_INET, SOCK_STREAM, 0 ); //建立socket
my_addr.sin_family = AF_INET; //選擇網(wǎng)絡(luò)類型為TCP/IP
my_addr.sin_addr.s_addr = inet_addr(“210.45.64.222");
my_addr.sin_port = htons( 8888 ); //選擇端口8888
addr_len = sizeof( struct sockaddr_in);
memset(&my_addr.sin_zero, ‘\0’, sizeof(my_addr.sin_zero));
bind(sockfd, (struct sockaddr *)&my_addr, addr_len); //綁定socket
listen(sockfd,10); //監(jiān)聽(tīng),等待連接,等待連接隊(duì)列最大長(zhǎng)度為10
簡(jiǎn)單的例子(續(xù))
27/37
While( 1 )
{
newsockfd = accept( sockfd, (struct sockaddr *)&my_addr, addr_len);
sendnum = send(newsockfd, msg, strlen(msg)+1, 0);
……
close(newsockfd);
}
close(sockfd);
阻塞與非阻塞(1)
28/37
阻塞函數(shù):指其完成指定的任務(wù)之前不允許程序調(diào)用另一個(gè)函數(shù),在Windows下還會(huì)阻塞本線程消息的發(fā)送。
eg: recv( ) ,當(dāng)socket工作在阻塞模式的時(shí)候,如果沒(méi)有數(shù)據(jù)的情況下調(diào)用該函數(shù),則當(dāng)前線程會(huì)被掛起,直到有數(shù)據(jù)為止。
非阻塞函數(shù):指操作啟動(dòng)之后,如果可以立即得到結(jié)果就返回結(jié)果,否則返回表示結(jié)果需要等待的錯(cuò)誤信息,不等待任務(wù)完成函數(shù)就返回。
使用非阻塞I/O的方式:select()
例子:
while(1){//執(zhí)行循環(huán) 一邊輸出一邊也不要忘了輸入
FD_ZERO(&wt_set); FD_ZERO(&rd_set);
FD_CLR(s,&wt_set); FD_CLR(s,&rd_set);
FD_SET(s,&wt_set); FD_SET(s,&rd_set);
timeout.tv_sec = 0;
timeout.tv_usec =500000;
z=select(s+1,&rd_set,&wt_set,NULL,&timeout);
if(FD_ISSET(s,&rd_set)){//有數(shù)據(jù)可讀
z=recv(s,&recvBuff,sizeof recvBuff-1,0);
29/37
阻塞與非阻塞(2)
30/37
在Berkeley socket函數(shù)部分中,不涉及網(wǎng)絡(luò)I/O、本地端工作的函數(shù)是非阻塞函數(shù)
在Berkeley socket函數(shù)部分中,網(wǎng)絡(luò)I/O的函數(shù)是可阻塞函數(shù),也就是它們可以阻塞執(zhí)行,也可以不阻塞執(zhí)行。這些函數(shù)都使用了一個(gè)socket,如果它們使用的socket是阻塞的,則這些函數(shù)是阻塞函數(shù);如果它們使用的socket是非阻塞的,則這些函數(shù)是非阻塞函數(shù)。
并發(fā)服務(wù)器
31/37
TCP服務(wù)器端(并發(fā)服務(wù)器)
socket( )
bind( )
listen( )
accept( )
send( )
recv( )
close( )
fork( ) //派生新進(jìn)程
close( )
主進(jìn)程在accept之后派生新進(jìn)程,然后主進(jìn)程繼續(xù)listen,處理新的連接請(qǐng)求
新進(jìn)程自行和客戶端通信,新進(jìn)程和主進(jìn)程搶占CPU
WinSock API
32/37
WinSock是一個(gè)基于Socket模型的API,在Microsoft Windows操作系統(tǒng)類中使用。
它在Berkeley接口函數(shù)的基礎(chǔ)之上,還增加了基于消息驅(qū)動(dòng)機(jī)制的Windows擴(kuò)展函數(shù)。
Winscok1.1只支持TCP/IP網(wǎng)絡(luò),WinSock2.0增加了對(duì)更多協(xié)議的支持。
Windows下的Socket編程(1)
33/37
和linux下基本相同,需要包含winsock2.h
需要使用Ws_32.lib,可以用以下語(yǔ)句通告程序編譯時(shí)調(diào)用該庫(kù):
#pragma comment(lib,"Ws2_32.lib") ;
WinSock以DLL的形式提供,在調(diào)用任何WinSock API之前,必須調(diào)用函數(shù)WSAStartup()進(jìn)行初始化,最后,調(diào)用函數(shù)WSACleanUp()作清理工作。
Windows下的Socket編程(2)
34/37
WSADATA wsd;
//設(shè)置WINSOCK的版本
WORD wVersionRequested=MAKEWORD(2,2);
WSAStartup(wVersionRequested,&wsd) ; //初始化
。。。。。。。。。
WSACleanUp();
Windows下的Socket編程(3)
35/37
MFC提供了兩個(gè)類CAsyncSocket和CSocket來(lái)封裝WinSock API,提供了更簡(jiǎn)單的網(wǎng)絡(luò)編程接口。
CAsyncSocket在較低層次上封裝了WinSock API,缺省情況下,使用該類創(chuàng)建的socket是非阻塞的socket,所有操作都會(huì)立即返回,如果沒(méi)有得到結(jié)果,返回WSAEWOULDBLOCK,表示是一個(gè)阻塞操作。
Windows下的Socket編程(4)
36/37
CSocket是CAsyncSocket的派生類,缺省情況下使用該類創(chuàng)建的socket是非阻塞的socket,但是CSocket的網(wǎng)絡(luò)I/O是阻塞的,它在完成任務(wù)之后才返回。
CSocket的阻塞不是建立在“阻塞”socket的基礎(chǔ)上,而是在“非阻塞”socket上實(shí)現(xiàn)的阻塞操作
網(wǎng)絡(luò)編程作業(yè)要求
37/37
不分組,每人獨(dú)立完成。
基于C/S或P2P結(jié)構(gòu),使用UDP或TCP協(xié)議皆可。
最好使用基本SOCKET API,不反對(duì)使用CAsyncSocket和
CSocket類,但不準(zhǔn)使用和傳輸相關(guān)的控件。
期末提交設(shè)計(jì)文檔,源碼,及可執(zhí)行文件。
提交時(shí)間為12月份,具體提交日期及提交方式待定。
可選題目I
BBS發(fā)帖程序
通過(guò)term方式(202.38.64.3:23)或者通過(guò)web方式
完成在test版發(fā)一貼的功能(多發(fā)會(huì)被永久封賬號(hào))
Bbs賬號(hào)和密碼使用命令行參數(shù)或其他方式設(shè)置,不要直接寫在程序里
對(duì)于term方式下的程序,要求能監(jiān)視程序運(yùn)行過(guò)程(也就是說(shuō)在程序運(yùn)行的時(shí)候要把服務(wù)器的輸出打印到屏幕)
Referrence:
http協(xié)議:http://en.wikipedia.org/wiki/HTTP
38/37
可選題目II
完成一個(gè)HTTP服務(wù)器
使用HTTP 1.1協(xié)議
支持最大至少10個(gè)并發(fā)連接(fork創(chuàng)建子進(jìn)程)
要求服務(wù)器程序運(yùn)行以后,能在瀏覽器中訪問(wèn)文件,正常顯示
Web server的根目錄使用命令行參數(shù)或者其他方式制定,不要寫在程序代碼里
文件不存在時(shí)返回瀏覽器404錯(cuò)誤
39/37
40/37
謝謝
聯(lián)系客服