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