|
網(wǎng)絡(luò)編程,一定離不開套接口;那什么是套接口呢?在Linux下,所有的I/O操作都是通過讀寫文件描述符而產(chǎn)生的,文件描述符是一個(gè)和打開的文件相關(guān)聯(lián)的整數(shù),這個(gè)文件并不只包括真正存儲在磁盤上的文件,還包括一個(gè)網(wǎng)絡(luò)連接、一個(gè)命名管道、一個(gè)終端等,而套接口就是系統(tǒng)進(jìn)程和文件描述符通信的一種方法。目前最常用的套接口是字:字節(jié)流套接口(基于TCP)和數(shù)據(jù)報(bào)套接口(基于UDP),當(dāng)然還有原始套接口(原始套接口提供TCP套接口和UDP套接口所不提供的功能,如構(gòu)造自己的TCP或UDP分組)等,我們這里主要介紹字節(jié)流套接口和數(shù)據(jù)報(bào)套接口。
要學(xué)習(xí)網(wǎng)絡(luò)編程,一定離不開網(wǎng)絡(luò)庫的函數(shù),下面就將常用的網(wǎng)絡(luò)函數(shù)和用法列出來供大家參考:
。、socket函數(shù):為了執(zhí)行網(wǎng)絡(luò)輸入輸出,一個(gè)進(jìn)程必須做的第一件事就是調(diào)用socket函數(shù)獲得一個(gè)文件描述符。
-----------------------------------------------------------------
#include<sys/socket.h>
int socket(int family,int type,int protocol);
返回:非負(fù)描述字---成功 -1---失敗
-----------------------------------------------------------------
|
第一個(gè)參數(shù)指明了協(xié)議簇,目前支持5種協(xié)議簇,最常用的有AF_INET(IPv4協(xié)議)和AF_INET6(IPv6協(xié)議);第二個(gè)參數(shù)指明套接口類型,有三種類型可選:SOCK_STREAM(字節(jié)流套接口)、SOCK_DGRAM(數(shù)據(jù)報(bào)套接口)和SOCK_RAW(原始套接口);如果套接口類型不是原始套接口,那么第三個(gè)參數(shù)就為0.
PS:
-----------------------------------------------------------------
個(gè)人一般用到的就是
if((socket_fd=socket(AF_INET,SOCK_STREAM,0)) == -1 )
perror("....");
if((socket_fd=socket(AF_INET,SOCK_DGRAM,0)) == -1 )
perror("....");
-----------------------------------------------------------------
|
2、connect函數(shù):當(dāng)用socket建立了套接口后,可以調(diào)用connect為這個(gè)套接字指明遠(yuǎn)程端的地址;如果是字節(jié)流套接口,connect就使用三次握手建立一個(gè)連接;如果是數(shù)據(jù)報(bào)套接口,connect僅指明遠(yuǎn)程端地址,而不向它發(fā)送任何數(shù)據(jù)。
-----------------------------------------------------------------
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr * servaddr,socklen_t
addrlen);
返回:0---成功 -1---失敗
-----------------------------------------------------------------
|
第一個(gè)參數(shù)是socket函數(shù)返回的套接口描述字;第二和第三個(gè)參數(shù)分別是一個(gè)指向套接口地址結(jié)構(gòu)的指針和該結(jié)構(gòu)的大小。
這些地址結(jié)構(gòu)的名字均已“sockaddr_”開頭,并以對應(yīng)每個(gè)協(xié)議族的唯一后綴結(jié)束。以IPv4套接口地址結(jié)構(gòu)為例,它以“sockaddr_in”命名,定義在頭文件;以下是結(jié)構(gòu)體的內(nèi)容:
------------------------------------------------------------------
struct in_addr {
in_addr_t s_addr;
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
-------------------------------------------------------------------
|
PS:
------------------------------------------------------------------
struct sockaddr_in dest;
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(port);//port就是指定的服務(wù)器ip,以及通信的端口
if (inet_aton(ip, (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
perror(ip);
exit(errno);
}
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror("Connect error\n");
exit(errno);
}
-------------------------------------------------------------------
|
。、bind函數(shù):為套接口分配一個(gè)本地IP 和協(xié)議端口,對于網(wǎng)際協(xié)議,協(xié)議地址是32位IPv4地址或128位IPv6地址與16位的TCP或UDP端口號的組合;如指定端口為0,調(diào)用bind時(shí)內(nèi)核將選擇一個(gè)臨時(shí)端口,如果指定一個(gè)通配IP地址,則要等到建立連接后內(nèi)核才選擇一個(gè)本地IP地址。
-------------------------------------------------------------------
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr * myaddr,socklen_t
addrlen);
返回:0---成功 -1---失敗
-------------------------------------------------------------------
|
第一個(gè)參數(shù)是socket函數(shù)返回的套接口描述字;第二和第第三個(gè)參數(shù)分別是一個(gè)指向特定于協(xié)議的地址結(jié)構(gòu)的指針和該地址結(jié)構(gòu)的長度。
PS:
-------------------------------------------------------------------
struct sockaddr_in server_address;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
-------------------------------------------------------------------
|
4、listen函數(shù):listen函數(shù)僅被TCP服務(wù)器調(diào)用,它的作用是將用sock創(chuàng)建的主動(dòng)套接口轉(zhuǎn)換成被動(dòng)套接口,并等待來自客戶端的連接請求。
-------------------------------------------------------------------
#include<sys/socket.h>
int listen(int sockfd,int backlog);
返回:0---成功 -1---失敗
-------------------------------------------------------------------
|
第一個(gè)參數(shù)是socket函數(shù)返回的套接口描述字;第二個(gè)參數(shù)規(guī)定了內(nèi)核為此套接口排隊(duì)的最大連接個(gè)數(shù)。由于listen函數(shù)第二個(gè)參數(shù)的原因,內(nèi)核要維護(hù)兩個(gè)隊(duì)列:以完成連接隊(duì)列和未完成連接隊(duì)列。未完成隊(duì)列中存放的是TCP連接的三路握手為完成的連接,accept函數(shù)是從以連接隊(duì)列中取連接返回給進(jìn)程;當(dāng)以連接隊(duì)列為空時(shí),進(jìn)程將進(jìn)入睡眠狀態(tài)。
PS:
-------------------------------------------------------------------
listen(sockfd,5);
-------------------------------------------------------------------
|
。怠ccept函數(shù):accept函數(shù)由TCP服務(wù)器調(diào)用,從已完成連接隊(duì)列頭返回一個(gè)已完成連接,如果完成連接隊(duì)列為空,則進(jìn)程進(jìn)入睡眠狀態(tài)。
-------------------------------------------------------------------
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *
cliaddr,socklen_t * addrlen);
回:非負(fù)描述字---成功 -1---失敗
-------------------------------------------------------------------
|
第一個(gè)參數(shù)是socket函數(shù)返回的套接口描述字;第二個(gè)和第三個(gè)參數(shù)分別是一個(gè)指向連接方的套接口地址結(jié)構(gòu)和該地址結(jié)構(gòu)的長度;該函數(shù)返回的是一個(gè)全新的套接口描述字;如果對客戶段的信息不感興趣,可以將第二和第三個(gè)參數(shù)置為空。
PS:
-------------------------------------------------------------------
struct sockaddr_in client_address;
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
-------------------------------------------------------------------
|
。丁net_pton函數(shù):將點(diǎn)分十進(jìn)制串轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序二進(jìn)制值,此函數(shù)對IPv4地址和IPv6地址都能處理。
-------------------------------------------------------------------
#include<sys/inet.h>
int inet_pton(int family,const char * strptr,void * addrptr);
返回:1---成功 0---輸入不是有效的表達(dá)格式 -1---失敗
int inet_aton(const char *strptr, struct in_addr *inp);
-------------------------------------------------------------------
|
第一個(gè)參數(shù)可以是AF_INET或AF_INET6:第二個(gè)參數(shù)是一個(gè)指向點(diǎn)分十進(jìn)制串的指針:第三個(gè)參數(shù)是一個(gè)指向轉(zhuǎn)換后的網(wǎng)絡(luò)字節(jié)序的二進(jìn)制值的指針。
。、inet_ntop函數(shù):和inet_pton函數(shù)正好相反,inet_ntop函數(shù)是將網(wǎng)絡(luò)字節(jié)序二進(jìn)制值轉(zhuǎn)換成點(diǎn)分十進(jìn)制串。
-------------------------------------------------------------------
#include
const char * inet_ntop(int family,const void *
addrptr,char * strptr,size_t len);
返回:指向結(jié)果的指針---成功 NULL---失敗
-------------------------------------------------------------------
|
第一個(gè)參數(shù)可以是AF_INET或AF_INET6:第二個(gè)參數(shù)是一個(gè)指向網(wǎng)絡(luò)字節(jié)序的二進(jìn)制值的指針;第三個(gè)參數(shù)是一個(gè)指向轉(zhuǎn)換后的點(diǎn)分十進(jìn)制串的指針;第四個(gè)參數(shù)是目標(biāo)的大小,以免函數(shù)溢出其調(diào)用者的緩沖區(qū)。
。、fork函數(shù):在網(wǎng)絡(luò)服務(wù)器中,一個(gè)服務(wù)端口可以允許一定數(shù)量的客戶端同時(shí)連接,這時(shí)單進(jìn)程是不可能實(shí)現(xiàn)的,而fork就分配一個(gè)子進(jìn)程和客戶端會(huì)話,當(dāng)然,這只是fork的一個(gè)典型應(yīng)用。
-------------------------------------------------------------------
#include<unistd.h>
pid_t fork(void);
返回:在子進(jìn)程中為0,在父進(jìn)程中為子進(jìn)程ID -1---失敗
-------------------------------------------------------------------
|
fork函數(shù)調(diào)用后返回兩次,父進(jìn)程返回子進(jìn)程ID,子進(jìn)程返回0。
有了上面的基礎(chǔ)知識,我們就可以進(jìn)一步了解TCP套接口和UDP套接口
1、TCP套接口
TCP套接口使用TCP建立連接,建立一個(gè)TCP連接需要三次握手,基本過程是服務(wù)器先建立一個(gè)套接口并等待客戶端的連接請求;當(dāng)客戶端調(diào)用 connect進(jìn)行主動(dòng)連接請求時(shí),客戶端TCP發(fā)送一個(gè)SYN,告訴服務(wù)器客戶端將在連接中發(fā)送的數(shù)據(jù)的初始序列號;當(dāng)服務(wù)器收到這個(gè)SYN后也給客戶端發(fā)一個(gè)SYN,里面包含了服務(wù)器將在同一連接中發(fā)送的數(shù)據(jù)的初始序列號;最后客戶在確認(rèn)服務(wù)器發(fā)的SYN。到此為止,一個(gè)TCP連接被建立。
下面就用一個(gè)例子來說明服務(wù)器和客戶是怎么連接的
-------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[]) {
int sockfd,numbytes;
char buf[100];
struct hostent *he;
struct sockaddr_in their_addr;
int i = 0;
//將基本名字和地址轉(zhuǎn)換
he = gethostbyname(argv[1]);
//建立一個(gè)TCP套接口
if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) {
perror("socket");
exit(1);
}
//初始化結(jié)構(gòu)體,連接到服務(wù)器的2323端口
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(2323);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero),8);
//和服務(wù)器建立連接
if(connect(sockfd,(struct sockaddr *)&their_addr,
sizeof(struct sockaddr))
==-1){
perror("connect");
exit(1);
}
//向服務(wù)器發(fā)送字符串"hello!"
if(send(sockfd,"hello!",6,0)==-1) {
perror("send");
exit(1);
}
//接受從服務(wù)器返回的信息
if((numbytes = recv(sockfd,buf,100,0))==-1) {
perror("recv");
exit(1);
}
buf[numbytes] = '';
printf("result:%s",buf);
close(sockfd);
return 0;
}
--------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include
main() {
int sockfd,new_fd;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int sin_size;
//建立TCP套接口
if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) {
perror("socket");
exit(1);
}
//初始化結(jié)構(gòu)體,并綁定2323端口
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(2323);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
//綁定套接口
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct
sockaddr))==-1)
{
perror("bind");
exit(1);
}
//創(chuàng)建監(jiān)聽套接口
if(listen(sockfd,10)==-1) {
perror("listen");
exit(1);
}
//等待連接
while(1) {
sin_size = sizeof(struct sockaddr_in);
perror("server is run");
//如果建立連接,將產(chǎn)生一個(gè)全新的套接字
if((new_fd = accept(sockfd,(struct sockaddr *)
&their_addr,&sin_size))==-1)
{
perror("accept");
exit(1);
}
//生成一個(gè)子進(jìn)程來完成和客戶端的會(huì)話,父進(jìn)程繼續(xù)監(jiān)聽
if(!fork()) {
//讀取客戶端發(fā)來的信息
if((numbytes = recv(new_fd,buff,strlen(buff),0))==-1)
{
perror("recv");
exit(1);
}
printf("%s",buff);
//將從客戶端接收到的信息再發(fā)回客戶端
if(send(new_fd,buff,strlen(buff),0)==-1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd);
}
close(sockfd);
}
------------------------------------------------------------------
|
現(xiàn)在讓我們來編譯這兩個(gè)程序:
root@linuxaid#gcc -o server server.c
root@linuxaid#gcc -o client client.c
|
然后在一臺計(jì)算機(jī)上先運(yùn)行服務(wù)器程序,再在另一個(gè)終端上運(yùn)行客戶端就會(huì)看到結(jié)果;如果不運(yùn)行服務(wù)器程序而先運(yùn)行客戶程序?qū)⒘⒓刺崾?quot;Connect: Connection refused",這就是TCP套接口的好處,如果是UDP套接口將會(huì)有一個(gè)延時(shí)才會(huì)得到錯(cuò)誤信息(UDP套接口后面有介紹)。
建立一個(gè)TCP連接需要三次握手,而斷開一個(gè)TCP則需要四個(gè)分節(jié)。當(dāng)某個(gè)應(yīng)用進(jìn)程調(diào)用close(主動(dòng)端)后(可以是服務(wù)器端,也可以是客戶端),這一端的TCP發(fā)送一個(gè)FIN,表示數(shù)據(jù)發(fā)送完畢;另一端(被動(dòng)端)發(fā)送一個(gè)確認(rèn),當(dāng)被動(dòng)端待處理的應(yīng)用進(jìn)程都處理完畢后,發(fā)送一個(gè)FIN到主動(dòng)端,并關(guān)閉套接口,主動(dòng)端接收到這個(gè)FIN后再發(fā)送一個(gè)確認(rèn),到此為止這個(gè)TCP連接被斷開。
|
|