之前說的用于進程間通信的幾種方式:消息signal、管道pipe、消息隊列msg、共享內存shm、信號量sem。都只適用于一臺主機上的進程間通信,那么如何實現兩臺計算機之間的進程通信呢?所以,來了解一下異地進程通信。
以下是OSI參考模型與TCP/IP參考模型的對應關系:
TCP/IP 協議組大體上分為三部分:
1.Internet 協議(IP)
2.傳輸控制協議(TCP)和用戶數據報文協議(UDP)
3.處于TCP 和UDP 之上的一組協議專門開發的應用程序。它們包括:TELNET,文件傳送協議(FTP),域名服務(DNS)和簡單的郵件傳送程序(SMTP)等許多協議。
應用層協議
流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向連接的通訊流。它使用了TCP協議。TCP 保證了數據傳輸的正確性和順序性。
數據報套接字(SOCK_DGRAM)
數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,并且不保證可靠,無差錯。使用數據報協議UDP協議。
原始套接字。
原始套接字允許對低層協議如IP或ICMP直接訪問,主要用于新的網絡協議實現的測試等。
TCP
UDP
具體函數的用法,就自己man了。
重點講一下套接字地址結構:
#include < netinet/in.h>
struct sockaddr
{
unsigned short sa_family; /* address族, AF_xxx */
char sa_data[14]; /* 14 bytes的協議地址 */
};
#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一樣大?。?/
};
-linux提供將點分格式的地址轉于長整型數之間的轉換函數。
inet_ntoa()能夠把網絡字節順序轉換為地址結構的數據。
socket() bind() connect()
listen() accept() send()
recv() sendto() shutdown()
recvfrom() close() getsockopt()
setsockopt() getpeername()
getsockname() gethostbyname()
gethostbyaddr() getprotobyname()
fcntl()
TCP連接,等待客戶端輸入,將內容發送給服務器,并獲取客戶端地址。
這里,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() 服務器的地址
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;
}
運行結果:
做個改進,以上代碼,只能一個客戶端連接上。因為TCP是基于點對點的,一個accept()對應一個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;
}
//會出現沒有活動的套接字仍然存在,會禁止綁定端口,出現錯誤:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態會保留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(); //父進程負責繼續監聽等待,子進程父子與已連接客戶端通信
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) //客戶端退出
{
//結束相應的server進程
close(clientfd);
exit(0); //僵尸進程
}
printf("from %sreceive : %s\n\n",client_addr,acbuf);
}
}
else //父進程
{
//返回while,繼續等待
}
}
//6.close()
close(fd);
return 0;
}
這里一定要注意,每結束一個客戶端,一定要關掉相應的文件描述符,并且結束掉子進程(僵尸進程),不然,隨著客戶端的增加,進程數會越來越大
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() 服務器的地址
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;
}
運行結果:
使用UDP連接,完成上述內容。但是發現,使用UDP,因為是面向無連接的,所以在沒有收到或者發送包之前,是無法得知源IP地址的。
那UDP如何知道客戶端的IP地址和端口呢?
1、由客戶端顯示地高速服務器IP地址和端口,發消息。
2、隱式的。服務器從收到的包頭中得到源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;
}
//收到客戶端的數據包之后,就可以知道客戶端地址
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;
}
運行結果:
會發現,此時是可以直接運行多個客戶端的,因為,UDP是面向無連接的,可以是一對多,多對一,多對多的,只要客戶端知道服務器地址,就可以連上。
<br>
Ps:本人理解有限,還未學習完,有錯請指出。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。