socket編程概述
Linux網絡編程實質上就是socket編程,IP地址可以確定一個主機,端口號可以確定主機中的一個進程,socket = IP + port(端口號)
這樣便確定了主機中運行的進程,網絡通信實質上就是進程通信。
我們知道連接分爲兩種,可靠的TCP連接與不可靠的UDP連接
,下面分別通過這兩種連接來熟悉socket編程.
一.TCP通信
- 服務端:
socket -> bind -> listen -> while(1){ accept -> doing ...} -> close
- 客戶端:
socket -> connect -> while (1) {doing ...} ->close
服務端詳細流程
int socket(int __domain, int __type, int __protocol)
- domain 使用的協議族:AF_INET(Ipv4), AF_INET6(Ipv6)
- type 代表連接的方式:SOCK_STREAM(TCP), SOCK_DGRAM(UDP)
- protocol 協議,一般爲0
服務端建立socket。
int bind(int __fd, const sockaddr *__addr, socklen_t __len)
- fd 之前創建的套接字
- addr 需要綁定的 地址族,IP地址, port號等, 一般使用struct sockaddr_in類型 進行綁定,然後再在bind函數進行類型強轉
- len addr的大小
將已經創建好的socket進行綁定好 IP地址,端口號等,如果想要進行網絡通信,就需要通過這個socket。
int listen(int __fd, int __n)
- fd socket套接字
- n 同時連接的客戶端的最大數量
在綁定好socket之後,開始進行監聽,注意監聽是非阻塞,只是說明服務端開始監聽socket
int accept(int __fd, sockaddr *__restrict__ __addr, socklen_t *__restrict__ __addr_len)
- fd socket套接字
- addr 接收到的client信息結構體, 一般使用struct sockaddr_in 類型再強轉
- addr_len client信息結構體的大小,注意是傳入地址
accept函數一般是阻塞狀態,用於接收連接的服務器,注意返回值是返回一個新的 newfd, 之後與服務器與客戶端通信就是通過這個套接字進行通信
注意:此時sockfd只是用於接收客戶端連接成功後的newfd套接字, 之後的通信都將在套接字newfd中進行, 也就是說,sockfd並不參與數據的交換。
close
close掉所有的newfd,sockfd
客戶端詳細流程
int socket(int __domain, int __type, int __protocol)
- domain 使用的協議族:AF_INET(Ipv4), AF_INET6(Ipv6)
- type 代表連接的方式:SOCK_STREAM(TCP), SOCK_DGRAM(UDP)
- protocol 協議,一般爲0
int connect(int __fd, const sockaddr *__addr, socklen_t __len)
- fd 之前創建的socket套接字
- addr 需要綁定的 地址族,IP地址, port號等, 一般使用struct sockaddr_in類型 進行綁定,然後再在connect函數進行類型強轉
- len addr的大小
這個connect與服務端的bind函數相對應,連接上服務端已經綁定好的socket
注意,在bind之前可能會遇到:bind: Address already in use 這種情況
出現的原因是綁定的套接字在close之後還需要等待TIME_WAIT一段時間才能夠進行重新綁定。
此時我們可以使用以下語句,讓套接字可以立刻再次使用:
int on = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
ERROR_CHECK(ret, -1, "setsockopt");
案例:服務端與客戶端進行TCP通信
server
:
int main()
{
int ret;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
ERROR_CHECK(sockfd, -1, "socket");
int on = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
ERROR_CHECK(ret, -1, "setsockopt");
struct sockaddr_in sockinfo;
bzero(&sockinfo, sizeof(sockinfo));
sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
sockinfo.sin_port = htons(2000);
sockinfo.sin_family = AF_INET;
ret = bind(sockfd, (sockaddr*)&sockinfo, sizeof(sockinfo));
ERROR_CHECK(ret, -1, "bind");
ret = listen(sockfd, 10);
ERROR_CHECK(ret, -1, "listen");
int newfd = accept(sockfd, NULL, NULL);
ERROR_CHECK(ret, -1, "accept");
char buf[100] = {0};
recv(newfd, buf, sizeof(buf), 0);
cout << "receiving from client:" << buf << endl;
send(newfd, "world", 5, 0);
close(sockfd);
close(newfd);
return 0;
}
client
:
int main()
{
int ret;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
ERROR_CHECK(sockfd, -1, "socket");
struct sockaddr_in sockinfo;
bzero(&sockinfo, sizeof(sockinfo));
sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
sockinfo.sin_port = htons(2000);
sockinfo.sin_family = AF_INET;
ret = connect(sockfd, (sockaddr *)&sockinfo, sizeof(sockinfo));
ERROR_CHECK(ret, -1, "connect");
send(sockfd, "hello", 5, 0);
char buf[100] = {0};
recv(sockfd, buf, sizeof(buf), 0);
cout << "receiving from server:" << buf << endl;
close(sockfd);
return 0;
}
output
:
二.UDP通信
udp是不可靠的連接方法,所以客戶端的流程與服務端的流程都比較簡單
-
服務端:
socket -> bind -> recvfrom/sendto
-
客戶端:
socket -> recvfrom/sendto
主要掌握以下兩個函數:
recvfrom(..., sockaddr *__restrict__ __addr, socklen_t *__restrict__ __addr_len)
前面參數與recv 相同,addr結構體用於存放發送方的IP地址與端口號。
sendto(..., const sockaddr *__addr, socklen_t __addr_len)
前面的參數與send相同,addr結構體用於存放目標地址IP與端口號
server
:
int main()
{
int ret;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
ERROR_CHECK(sockfd, -1, "socket");
struct sockaddr_in sockinfo;
socklen_t len = sizeof(sockinfo);
bzero(&sockinfo, sizeof(sockinfo));
sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
sockinfo.sin_port = htons(2012);
sockinfo.sin_family = AF_INET;
ret = bind(sockfd, (sockaddr *)&sockinfo, len);
ERROR_CHECK(ret, -1, "bind");
char buf[100] = {0};
struct sockaddr_in client;
recvfrom(sockfd, buf, sizeof(buf), 0, (sockaddr*)&client, &len);
cout << " client " << inet_ntoa(client.sin_addr) << " :" << ntohs(client.sin_port) << endl;
cout << "receiving :" << buf << endl;
sendto(sockfd, "world", 5, 0, (sockaddr*)&sockinfo, len);
close(sockfd);
return 0;
}
client
:
int main()
{
int ret;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
ERROR_CHECK(sockfd, -1, "socket");
struct sockaddr_in sockinfo;
socklen_t len = sizeof(sockinfo);
bzero(&sockinfo, sizeof(sockinfo));
sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
sockinfo.sin_port = htons(2012);
sockinfo.sin_family = AF_INET;
sendto(sockfd, "hello", 5, 0, (sockaddr*)&sockinfo, len);
char buf[100] = {0};
recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
cout << "receving from server:" << buf << endl;
close(sockfd);
return 0;
}
output
: