系統(tǒng)運維
之前說的用于進程間通信的幾種方式:消息signal、管道pipe、消息隊列msg、共享內(nèi)存shm、信號量sem。都只適用于一臺主機上的進程間通信,那么如何實現(xiàn)兩臺計算機之間的進程通信呢?所以,來了解一下異地進程通信。
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供白堿灘網(wǎng)站建設(shè)、白堿灘做網(wǎng)站、白堿灘網(wǎng)站設(shè)計、白堿灘網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、白堿灘企業(yè)網(wǎng)站模板建站服務(wù),10多年白堿灘做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。1 異地進程通信 協(xié)議層為雙方的主機通信進程分配“端口”和緩沖區(qū),以便異地進程間的通信。 1.1TCP/IP協(xié)議以下是OSI參考模型與TCP/IP參考模型的對應(yīng)關(guān)系:
1.1.1 TCP/IP協(xié)議族TCP/IP 協(xié)議組大體上分為三部分:
1.Internet 協(xié)議(IP)
2.傳輸控制協(xié)議(TCP)和用戶數(shù)據(jù)報文協(xié)議(UDP)
3.處于TCP 和UDP 之上的一組協(xié)議專門開發(fā)的應(yīng)用程序。它們包括:TELNET,文件傳送協(xié)議(FTP),域名服務(wù)(dns)和簡單的郵件傳送程序(SMTP)等許多協(xié)議。
應(yīng)用層協(xié)議
流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向連接的通訊流。它使用了TCP協(xié)議。TCP 保證了數(shù)據(jù)傳輸?shù)恼_性和順序性。
數(shù)據(jù)報套接字(SOCK_DGRAM)
數(shù)據(jù)報套接字定義了一種無連接的服務(wù),數(shù)據(jù)通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠,無差錯。使用數(shù)據(jù)報協(xié)議UDP協(xié)議。
原始套接字。
原始套接字允許對低層協(xié)議如IP或ICMP直接訪問,主要用于新的網(wǎng)絡(luò)協(xié)議實現(xiàn)的測試等。
TCP
UDP
具體函數(shù)的用法,就自己man了。
重點講一下套接字地址結(jié)構(gòu):
#include < netinet/in.h>
struct sockaddr
{
unsigned short sa_family; /* address族, AF_xxx */
char sa_data[14]; /* 14 bytes的協(xié)議地址 */
};
sa_family的取值,一般來說,IPV4使用“AF_INET”
sa_data包含了一些遠程電腦的地址、端口和套接字的數(shù)目,里面的數(shù)據(jù)是雜溶在一起的。一般我們不用這個結(jié)構(gòu)體,因為我們一般使用的地址都是IP+端口號。比如:IP192.168.159.2 port3306 。這樣來記錄地址。所以一般使用下面這個地址結(jié)構(gòu),而知數(shù)據(jù)類型是等效的,可以互相轉(zhuǎn)換。
#include < netinet/in.h>
struct sockaddr_in {
short int sin_family; /* Internet地址族 */
unsigned short int sin_port; /* 端口號 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 添0(和struct sockaddr一樣大?。?/
};
2.2.2 字節(jié)序列轉(zhuǎn)換
因為每一個機器內(nèi)部對變量的字節(jié)存儲順序不同(有的系統(tǒng)是高位在前,底位在后,而有的系統(tǒng)是底位在前,高位在后 ),而網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)大家是一定要統(tǒng)一順序的。所以對與內(nèi)部字節(jié)表示順序和網(wǎng)絡(luò)字節(jié)順序不同的機器,就一定要對數(shù)據(jù)進行轉(zhuǎn)換。
htons()——“Host to Network Short”-linux提供將點分格式的地址轉(zhuǎn)于長整型數(shù)之間的轉(zhuǎn)換函數(shù)。
inet_addr()能夠把一個用數(shù)字和點表示IP 地址的字符串轉(zhuǎn)換成一個無符號長整型。inet_ntoa()能夠把網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換為地址結(jié)構(gòu)的數(shù)據(jù)。
2.2.4基本套接字調(diào)用socket() bind() connect()
listen() accept() send()
recv() sendto() shutdown()
recvfrom() close() getsockopt()
setsockopt() getpeername()
getsockname() gethostbyname()
gethostbyaddr() getprotobyname()
fcntl()
TCP連接,等待客戶端輸入,將內(nèi)容發(fā)送給服務(wù)器,并獲取客戶端地址。
這里,getsocketname()表示獲得本地(自己)的地址;
getpeername()表示獲得連接上的客戶端的地址(源IP地址)。
<br>
server.c
#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h> //sockaddr_in
#include < stdio.h>
#include < string.h>
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int addrLen = 0;
char acbuf[20] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //連上的客戶端的地址
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
//4.阻塞 等待 accept()
clientfd = accept(fd,NULL,NULL);
if(clientfd == -1)
{
perror(accept);
return -1;
}
//獲取客戶端地址
addrLen = sizeof(struct sockaddr_in);
ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);
if(ret == -1)
{
perror(getpeername);
return -1;
}
printf(client login.\\nip: %s , port: %d\\n,inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
//5.通信
while(1)
{
memset(acbuf,0,20);
if (read(clientfd,acbuf,20) > 0)
{
printf(receive: %s\\n,acbuf);
}
}
//6.close()
close(fd);
return 0;
}
client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //sockaddr_in
#include <stdio.h>
#include <string.h>
int main()
{
int fd;
int ret;
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
//1.socket();
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.連接connect() 服務(wù)器的地址
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1234);
serAddr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(connect);
return -1;
}
//3.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
write(fd,acbuf,strlen(acbuf));
}
//4.close()
close(fd);
return 0;
}
運行結(jié)果:
做個改進,以上代碼,只能一個客戶端連接上。因為TCP是基于點對點的,一個accept()對應(yīng)一個connnect()。要想連接多個客戶端,就得使用fork(),一個進程用來專門阻塞等待客戶端的連接,一個用來處理與已連接上客戶端的通信。
代碼如下:
server.c
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int addrLen = 0;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //連上的客戶端的地址
signal(SIGCHILD,SIG_IGN);
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//會出現(xiàn)沒有活動的套接字仍然存在,會禁止綁定端口,出現(xiàn)錯誤:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態(tài)會保留2-4分鐘
//if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
//{
// perror(setsockopet error\\n);
// return -1;
//}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
while(1) {
//4.阻塞等待 accept()
clientfd = accept(fd,NULL,NULL);
if(clientfd == -1)
{
perror(accept);
return -1;
}
pid = fork(); //父進程負責(zé)繼續(xù)監(jiān)聽等待,子進程父子與已連接客戶端通信
if(pid == -1)
{
perror(fork);
return -1;
}
if(pid == 0) //子進程
{
//獲取客戶端地址
addrLen = sizeof(struct sockaddr_in);
ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);
if(ret == -1)
{
perror(getpeername);
return -1;
}
sprintf(client_addr,ip: %s , port: %d\\n,\\
inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
printf(client longin.\\n%s\\n,client_addr);
//5.通信
while(1)
{
memset(acbuf,0,20);
if (read(clientfd,acbuf,20) == 0) //客戶端退出
{
//結(jié)束相應(yīng)的server進程
close(clientfd);
exit(0); //僵尸進程
}
printf(from %sreceive : %s\\n\\n,client_addr,acbuf);
}
}
else //父進程
{
//返回while,繼續(xù)等待
}
}
//6.close()
close(fd);
return 0;
}
這里一定要注意,每結(jié)束一個客戶端,一定要關(guān)掉相應(yīng)的文件描述符,并且結(jié)束掉子進程(僵尸進程),不然,隨著客戶端的增加,進程數(shù)會越來越大
client.c
int main()
{
int fd;
int ret;
int addrLen;
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
struct sockaddr_in myAddr = {0};
//1.socket();
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.連接connect() 服務(wù)器的地址
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1234);
serAddr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(connect);
return -1;
}
//獲取自己的地址
addrLen = sizeof(struct sockaddr_in);
ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);
if(ret == -1)
{
perror(getsockname);
return -1;
}
printf(client---ip: %s , port: %d\\n,\\
inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
//3.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
write(fd,acbuf,strlen(acbuf));
}
//4.close()
close(fd);
return 0;
}
運行結(jié)果:
練習(xí)2-UDP使用UDP連接,完成上述內(nèi)容。但是發(fā)現(xiàn),使用UDP,因為是面向無連接的,所以在沒有收到或者發(fā)送包之前,是無法得知源IP地址的。
那UDP如何知道客戶端的IP地址和端口呢?
1、由客戶端顯示地高速服務(wù)器IP地址和端口,發(fā)消息。
2、隱式的。服務(wù)器從收到的包頭中得到源IP和端口號。
server.c
int main()
{
int sockfd;
int ret;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0};
struct sockaddr_in clientAddr = {0};
int addrLen = sizeof(struct sockaddr_in);
int reuse = 0;
//1.socket()
sockfd = socket(PF_INET,SOCK_DGRAM,0);
if(sockfd == -1)
{
perror(socket);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1235);
addr.sin_addr.s_addr = inet_addr(127.0.0.1);
ret = bind(sockfd,(struct sockaddr *)&addr,addrLen);
if(ret == -1)
{
perror(bind);
return -1;
}
//3.通信
while(1)
{
memset(acbuf,0,20);
if(recvfrom(sockfd, acbuf, 100,0,(struct sockaddr *)&clientAddr,&addrLen) == -1)
{
perror(recvfrom);
return -1;
}
//收到客戶端的數(shù)據(jù)包之后,就可以知道客戶端地址
sprintf(client_addr, ip: %s , port: %d\\n,\\
inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
printf(receive from %s: %s\\n,client_addr,acbuf);
}
//4.close
close(sockfd);
return 0;
}
client.c
int main()
{
int sockfd;
int ret;
int addrLen = sizeof(struct sockaddr_in);
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
struct sockaddr_in myAddr = {0};
//1.socket()
sockfd = socket(PF_INET,SOCK_DGRAM,0);
if(sockfd == -1)
{
perror(socket);
return -1;
}
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1235);
serAddr.sin_addr.s_addr = inet_addr(127.0.0.1);
//2.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
sendto(sockfd, acbuf, 20,0,(struct sockaddr *)&serAddr,addrLen);
//獲取自己的地址
ret = getsockname(sockfd,(struct sockaddr *)&myAddr,&addrLen);
if(ret == -1)
{
perror(getsockname);
return -1;
}
printf(client---ip: %s , port: %d\\n\\n,\\
inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
}
//3.close
close(sockfd);
return 0;
}
運行結(jié)果:
會發(fā)現(xiàn),此時是可以直接運行多個客戶端的,因為,UDP是面向無連接的,可以是一對多,多對一,多對多的,只要客戶端知道服務(wù)器地址,就可以連上。
<br>
Ps:本人理解有限,還未學(xué)習(xí)完,有錯請指出。
分享標題:socket網(wǎng)絡(luò)編程之TCP、UDP
文章源于:http://www.rwnh.cn/article46/cpjshg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、網(wǎng)站維護、建站公司、品牌網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計公司、App設(shè)計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)