如何连接Linux上的服务器 网络编程,Linux 网络编程 一
一、網(wǎng)絡(luò)編程基礎(chǔ)
網(wǎng)絡(luò)編程本身是一門很大的學(xué)問,涉及到的東西也很多,尤其是各種協(xié)議。先看圖:
正如上圖所示,網(wǎng)絡(luò)編程中包含五大層面(也有區(qū)分六個(gè)層面),從應(yīng)用層到物理層可以明顯看出 越往下越接近計(jì)算機(jī)硬件。自己并不是專業(yè)網(wǎng)絡(luò)編程的工程師,所以僅對(duì)這五大層面有一點(diǎn)點(diǎn)粗淺的了解,這篇文章網(wǎng)絡(luò)編程技巧博主寫的比較詳細(xì). 平時(shí)大多數(shù)所謂網(wǎng)絡(luò)編程,其實(shí)是在傳輸層、網(wǎng)絡(luò)層方面.
二、socket編程
首先,socket(套接字)編程應(yīng)該屬于傳輸層,主要實(shí)現(xiàn)的是端到端的通信,非常類似于很久很久以前的固話通信,應(yīng)用程序可以通過(guò)它發(fā)送或者接受數(shù)據(jù),可以對(duì)它進(jìn)行像文件似得讀寫、關(guān)閉等操作。套接字允許應(yīng)用程序?qū)/O插入網(wǎng)絡(luò)中,并與網(wǎng)絡(luò)中的其他應(yīng)用程序進(jìn)行通信。
其次,網(wǎng)絡(luò)中兩臺(tái)或多臺(tái)主機(jī)之間進(jìn)行通信,必須知道對(duì)應(yīng)主機(jī)的地址,也就是其IP地址,但是只知道IP地址是遠(yuǎn)遠(yuǎn)不夠的,試想如你在本機(jī)A發(fā)送了一個(gè)消息,另一個(gè)臺(tái)主機(jī)B也接收到到了該消息,但是到底是B主機(jī)的哪一個(gè)進(jìn)程接受并處理該消息?就像你用QQ給B發(fā)送消息,但是B不可能通過(guò)陌陌收到該消息。 因此,相互通信的主機(jī)之間還必須確定一一對(duì)應(yīng)的消息處理接口--端口。端口的存在,主要是為了確認(rèn)消息一一對(duì)應(yīng)性。另外,端口號(hào)其實(shí)就是一個(gè)從0開始的到65535之間的一個(gè)整型數(shù)字,0~1023端口,也就是常說(shuō)的靜態(tài)端口,已被操作系統(tǒng)另做它用(http,https,ftp等各種協(xié)議占用),我們自己所能使用的端口范圍只能從1024開始,即動(dòng)態(tài)端口取值[1024,65535].
可是看出,若要進(jìn)行網(wǎng)絡(luò)間通信,socket至少要包含IP+port兩個(gè)方面,其實(shí)事實(shí)也是如此.還是以有線電話做為類比,socket其實(shí)就是自己家中的一部電話,其中IP就是家庭地址,port就是自己家的電話號(hào)碼,當(dāng)要給別人打電話時(shí),別人家當(dāng)然也必須有自己的座機(jī)和專屬于該座機(jī)的號(hào)碼.
或許我們也能猜出,socket編程是網(wǎng)絡(luò)編程里邊必不可少且及其重要的一個(gè)環(huán)節(jié).
三、Linux+socket實(shí)踐
1、目的
熟悉Linux(這里用Ubuntu16.04版本,其他版本類似)下socket編程基本流程,掌握socket編程基本原理,搞懂Linux下socket編程所必須的函數(shù)及其用法.
實(shí)驗(yàn):在本地模擬兩臺(tái)機(jī)器,服務(wù)器和客戶端,服務(wù)器監(jiān)聽客戶端信息并能發(fā)送廣播,客戶端可以主動(dòng)給服務(wù)器發(fā)送消息,其中消息的輸入是從標(biāo)準(zhǔn)輸入設(shè)備輸入,并輸出到標(biāo)準(zhǔn)輸出--Linux 終端.
開始之前必須了解一點(diǎn) 什么是文件描述符,在Unix Linux系統(tǒng)中,文件描述符是一個(gè)非負(fù)整數(shù),其存在作用更像一個(gè)索引,系統(tǒng)內(nèi)核通過(guò)該"索引"找到對(duì)應(yīng)的文件、設(shè)備、外設(shè)、安裝的軟件等等, 并通過(guò)描述符對(duì)它們進(jìn)行操作。總而言之,文件描述符對(duì)應(yīng)了系統(tǒng)上的所有文件,這里的文件并非"傳統(tǒng)意義上的普通文件",而是指Linux系統(tǒng)內(nèi)核所能管理1的一切,包含文檔、文件、硬件設(shè)備、系統(tǒng)軟件等等。這也體現(xiàn)了Linux系統(tǒng)的設(shè)計(jì)思想----把一切視作文件.
2、必要接口
1)、socket函數(shù)
既然socket這么重要,來(lái)看它到底是個(gè)什么東西.在Linux終端執(zhí)行:man socket,出現(xiàn):
通過(guò)Linux手冊(cè)查詢可以知道該函數(shù)所必須的頭文件,函數(shù)聲明和函數(shù)描述等信息.從[DESCRIPTION]字段可知,函數(shù)創(chuàng)建了一個(gè)用于通信的端點(diǎn)并返回該端點(diǎn)的描述符,若創(chuàng)建成功,返回創(chuàng)建套接字的文件描述符,否則返回一負(fù)數(shù).
函數(shù)聲明 int socket(int domain,int type,int protocol);
參數(shù) domain:表示創(chuàng)建該socket所使用的通訊協(xié)議家族--地址族,現(xiàn)在一般用IPv4協(xié)議,所以通常會(huì)選擇AF_INET;
參數(shù)type:指定所需的通信類型。包括數(shù)據(jù)流(SOCK_STREAM)TCP協(xié)議、數(shù)據(jù)報(bào)(SOCK-DGRAM)UDP協(xié)議和原始類型(S0CK_RAW)新網(wǎng)格協(xié)議的開發(fā)測(cè)試.
參數(shù)protocol:說(shuō)明該套接字使用的協(xié)議族中的特定協(xié)議。如果不希望特別指定使用的協(xié)議,則置為0,使用默認(rèn)的連接模式.
若要進(jìn)行 基于TCP IP的網(wǎng)絡(luò)開發(fā)測(cè)試,則函數(shù)創(chuàng)建方式一般為:
int listenfd = socket(AF_INET,SOCK_STREAM,0);
2)、bind函數(shù)
既然有了一部“電話”,那么就需要為該電話綁定唯一的“所屬地址”,同樣Linux命令行執(zhí)行:man bind,同樣函數(shù)聲明為:
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
從手冊(cè)的描述中可以看出,當(dāng)成功創(chuàng)建socket套接字后,調(diào)用該函數(shù)可以將所創(chuàng)建的套接字(sockfd)和指定的地址(addr)綁定.
地址是由這樣一個(gè)結(jié)構(gòu)體指定:
struct sockaddr {
sa_family_t sa_family; //地址族
char sa_data[14]; //14字節(jié)的協(xié)議地址
}
上面struct sockaddr是通用地址,在網(wǎng)絡(luò)編程中 internet sockaddr使用下面地址,兩種地址可以互換:
struct sockaddr_in {
short int sin_family; /* 地址族,AF_xxx 在socket編程中只能是AF_INET */
unsigned short int sin_port; /* 端口號(hào) (使用網(wǎng)絡(luò)字節(jié)順序) */
struct in_addr sin_addr; /* 存儲(chǔ)IP地址 4字節(jié) */
unsigned char sin_zero[8]; /* 總共8個(gè)字節(jié),實(shí)際上沒有什么用,只是為了和struct sockaddr保持一樣的長(zhǎng)度 */
};
bind()函數(shù)的第三個(gè)參數(shù)表示地址所占字節(jié)長(zhǎng)度,socklen_t本質(zhì)上是一個(gè) unsigned int宏定義.
可以通過(guò)這樣方式指定地址:
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5188);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
首先聲明網(wǎng)絡(luò)接口地址結(jié)構(gòu),在給該地址賦值前必須將其清空.依次設(shè)置該地址的地址族、IP和端口(這里隨便設(shè)置了一個(gè)),上邊出現(xiàn)另一個(gè)新函數(shù)htons,同樣終端下man htons,可知該函數(shù)的主要作用是將主機(jī)字節(jié)序轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序,關(guān)于這兩個(gè)字節(jié)序后續(xù)再深入研究.這里可以理解為:htons()的主要作用就是將十進(jìn)制的ip地址和端口號(hào)轉(zhuǎn)化為網(wǎng)絡(luò)可以識(shí)別的"東東".
至此,基本可以完成座機(jī)的安裝入戶和號(hào)碼綁定:
bing(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
3)、listen監(jiān)聽函數(shù)
對(duì)于我們的服務(wù)器而言,它需要監(jiān)聽來(lái)自客戶端發(fā)來(lái)的消息,Linix終端中 man listen可以看到詳細(xì)信息. 函數(shù)聲明為:
int listen(int socdfd,int backlog);
其中參數(shù)sockfd代指所要監(jiān)聽的套接字文件描述符,參數(shù)backlog表示在套接字掛起時(shí),所能接受請(qǐng)求的最大隊(duì)列長(zhǎng)度.函數(shù)執(zhí)行成功返回 0,否則返回 -1.
必須說(shuō)明一點(diǎn),當(dāng)調(diào)用該函數(shù)后,參數(shù)socdfd所指定的套接字將變?yōu)楸粍?dòng)套接字,所謂被動(dòng)套接字,是指其只能用來(lái)接收來(lái)自其他用戶的鏈接請(qǐng)求. 類似于改變了套接字的狀態(tài),使其只能用于接收.
4)、accept 接收函數(shù)
對(duì)于我們的服務(wù)器而言,由于其只具備接收功能,因此必須創(chuàng)建一個(gè)接受函數(shù):
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函數(shù)參數(shù)不言自明,參數(shù)1sockfd表示服務(wù)器socket描述符,參數(shù)2是指客戶端的協(xié)議地址,參數(shù)3為地址長(zhǎng)度. 函數(shù)成功返回監(jiān)聽的等待隊(duì)列中第一個(gè)套接字的描述符.
3、服務(wù)器實(shí)現(xiàn)
服務(wù)器的功能是監(jiān)聽客戶端發(fā)來(lái)的消息,并將消息廣播給客戶端.因此需要一個(gè)循環(huán)實(shí)時(shí)監(jiān)聽客戶端發(fā)來(lái)的消息,在本地構(gòu)建一個(gè)簡(jiǎn)單的服務(wù)器如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(void){
int listenfd;
if(( listenfd = socket(PF_INET,SOCK_STREAM,0)) < 0){
ERR_EXIT("socket");
}
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5188);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
//serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
ERR_EXIT("bind");
//一旦監(jiān)聽,則為被動(dòng)套接字(只能接受連接,調(diào)用accep函數(shù)之前調(diào)用),這里隨便給了一個(gè)最大隊(duì)列長(zhǎng)度
if(listen(listenfd,100)< 0)
ERR_EXIT("listen");
//聲明一個(gè)地址,用于存儲(chǔ)客戶端鏈接時(shí)的協(xié)議地址
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn; //返回的一個(gè)主動(dòng)套接字
if((conn= accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept");
char recvbuff[1024];
while(1){
memset(recvbuff,0,sizeof(recvbuff));
int ret = read(conn,recvbuff,sizeof(recvbuff));
fputs(recvbuff,stdout);
write(conn,recvbuff,ret);
}
close(listenfd);
return 0;
}
其中用到了幾個(gè)非socket API的函數(shù):
ssize_t read(int fd,void *buf,size_t count);
ssize_t write(int fd, const void *buf, size_t count);
read()函數(shù):負(fù)責(zé)從fd所指定文件描述符讀取字節(jié)大小為count的數(shù)據(jù)到buf中.若成功返回實(shí)際讀取到的字節(jié)大小,否則返回負(fù)數(shù),返回0表示讀取到文件結(jié)束.
write():將buf中的count個(gè)字節(jié)內(nèi)容寫入文件描述符fd.成功時(shí)返回寫的字節(jié)數(shù).
4、客戶端實(shí)現(xiàn)
客戶端的實(shí)現(xiàn)和服務(wù)器的實(shí)現(xiàn)之間大同小異,同樣都需要 ” 安裝電話 “ ,但是客戶端的功能僅在于向外”撥打電話“. 區(qū)別在于客戶端是主動(dòng)發(fā)起連接請(qǐng)求,所以它必須知道自己所要連接的目標(biāo),之后服務(wù)器才有響應(yīng).同樣客戶端并不需要監(jiān)聽,只需要接收到服務(wù)器的廣播即可. 發(fā)起連接請(qǐng)求需要函數(shù) connect:
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
在上述連接函數(shù)中,參數(shù)sockfd表示本機(jī)(客戶端)的socket套接字描述符,參數(shù)addr表示服務(wù)器端的地址,參數(shù)3表示地址長(zhǎng)度.
代碼實(shí)現(xiàn):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(void){
int sock;
if(( sock = socket(PF_INET,SOCK_STREAM,0)) < 0){
ERR_EXIT("socket");
}
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(5188);
// serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//發(fā)起連接
connect(sock,(struct sockadddr*)&serveraddr,sizeof(serveraddr));
char recvbuf[1024]={0};
char sendbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL){
write(sock,sendbuf,strlen(sendbuf));
read(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;
}
上述函數(shù)功能就是從客戶端主動(dòng)向服務(wù)器發(fā)送連接請(qǐng)求,并在客戶端機(jī)器的標(biāo)準(zhǔn)設(shè)備上如字符,服務(wù)器接受并返回. 實(shí)現(xiàn)兩臺(tái)機(jī)器通信的模擬.
5、結(jié)果
效果如下圖:
用gcc編譯上述兩個(gè)文件,首先啟動(dòng)服務(wù)器,之后啟動(dòng)客戶端.在客戶端隨便輸入字符,服務(wù)器解收到并廣播返回. 至此基本完成目的.
三、總結(jié)
目前來(lái)看,創(chuàng)建服務(wù)器的一般流程是:
1.創(chuàng)建socket套接字(`socket`函數(shù));
2.創(chuàng)建服務(wù)器地址,地址包含協(xié)議族、IP和端口號(hào)(`const struct sockaddr*`);
3.綁定套接字和服務(wù)器地址(bind函數(shù));
4.系統(tǒng)監(jiān)聽服務(wù)器,一旦監(jiān)聽則該套接字變?yōu)楸粍?dòng)套接字,只能用于接收數(shù)據(jù)(`listen`函數(shù));
5.作為服務(wù)器,應(yīng)該能接收客戶端信息(`accept`函數(shù)),該函數(shù)返回一個(gè)主動(dòng)套接字;
基于以上步驟,基本能搭建一個(gè)簡(jiǎn)單的服務(wù)器.
客戶端的搭建相比而言簡(jiǎn)單許多:
1.創(chuàng)建用于連接的套接字;
2.將套接字和服務(wù)器地址連接;
3.發(fā)送消息
網(wǎng)絡(luò)編程畢竟浪大水深,畢竟初涉,慢慢填充.
總結(jié)
以上是生活随笔為你收集整理的如何连接Linux上的服务器 网络编程,Linux 网络编程 一的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: w ndows7与XP哪个好,windo
- 下一篇: count返回0_MySQL实战 | 1