套接字簡介:
套接字是網絡編程中的一種通信機制,是支持TCP/IP的網絡通信的基本操作單元,可以看做是不同主機之間的進程進行雙向通信的端點,簡單的說就是通信的兩方的一種約定,用套接字中的相關函數來完成通信過程。應用層通過傳輸層進行數據通信時,TCP和UDP會遇到同時為多個應用程序進程提供並發服務的問題。憑借這種機制,客戶/服務器系統的開發工作既可以在本地單機上進行,也可以跨網絡進行,Linux所提供的功能(如打印服務,ftp等)通常都是通過套接字來進行通信的,套接字的創建和使用與管道是有區別的,因為套接字明確地將客戶和服務器區分出來,套接字可以實現將多個客戶連接到一個服務器。
套接字創建流程以及所用到的函數:
首先socket通信中需要以下幾個結構體:
struct in_addr
{
in_addr_t s_addr; //32位IPV4地址
}
struct sockaddr_in
{
uint8_t sin_len; //結構體長度
sa_family_t sin_family; //協議族
in_port_t sin_port; //端口號
struct in_addr sin_addr; //32位 IPV4地址
char sin_zero[8] //保留
}
1、創建套接字:
int socket(int domain, int type, int protocol); //使用前創建一個新的套接字
參數1(domain):選擇創建的套接字所用的協議族;常用的有以下選擇:
AF_INET : IPv4協議;
AF_INET6: IPv6協議;
AF_LOCAL: Unix域協議;
AF_ROUTE:路由套接口;
AF_KEY :密鑰套接口。
參數2(type):指定套接口類型,所選類型有:
SOCK_STREAM:字節流套接字;
SOCK_DGRAM : 數據報套接字;
SOCK_RAW : 原始套接口。
procotol: 使用的特定協議,一般使用默認協議(NULL)。
2、指定本地地址
int bind(int socket, const struct sockaddr *address, socklen_t address_len); //將套接字地址與所創建的套接字號聯系起來。
參數1(socket) : 是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(address):指向特定協議的地址指針。
參數3(address_len):上面地址結構的長度。
返回值:沒有錯誤,bind()返回0,否則SOCKET_ERROR。
3、建立套接字連接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //客戶端請求連接
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len); //服務器接受連接工作
參數1(socket) : 是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(address):指向特定協議的地址指針。
參數3(addrlen):上面地址結構的長度。
返回值:沒有錯誤,bind()返回0,否則SOCKET_ERROR。
4、監聽連接
int listen(int sockfd, int backlog); //用於面向連接服務器,表明它願意接收連接。
參數1(sockfd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(backlog):所監聽的端口隊列大小。
5、數據傳輸
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //發送數據
ssize_t recv(int sockfd, void *buf, size_t len, int flags); 接收數據
參數1(buf):指向存有傳輸數據的緩沖區的指針
參數2(len):緩沖區長度。
參數3(flags):flags的值或為0,或由下面常值中選擇一個:
MSGD_DONTROUTE:不查路由表 (send可選)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可選)
MSG_OOB : 發送或接收帶外數據。 (send、recv均可選)
MSG_PEEK : 查看外來的消息 (recv可選)
MSG_WAITALL : 等待所有數據 (recv可選)
6、關閉套接字
int close(int fd); //關閉套接字
參數(fd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
TCP與UDP::
(TCP)傳輸控制協議,面向連接。是一種提供可靠數據傳輸的通用協議。
(UDP)用戶數據報協議,是一個面向無連接的協議。采用該協議不需要兩個應用程序先建立連接。UDP協議不提供差錯恢復,不能提供數據重傳,因此該協議傳輸數據安全性差。
TCP示現客戶—服務器程序:
TCP客戶—服務器程序的執行流程圖:
TCP客戶—服務器程序代碼:
Ser.cpp:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define SERVER_PORT 5050 //端口號
#define SERVER_IP "192.168.3.254" //服務器ip
#define QUEUE_SIZE 5 //所監聽端口隊列大小
int main(int argc, char *argv[])
{
//創建一個套接字,並檢測是否創建成功
int sockSer;
sockSer = socket(AF_INET, SOCK_STREAM, 0);
if(sockSer == -1){
perror("socket");
}
//設置端口可以重用,可以多個客戶端連接同一個端口,並檢測是否設置成功
int yes = 1;
if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
perror("setsockopt");
}
struct sockaddr_in addrSer,addrCli; //創建一個記錄地址信息的結構體
addrSer.sin_family = AF_INET; //所使用AF_INET協議族
addrSer.sin_port = htons(SERVER_PORT); //設置地址結構體中的端口號
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); //設置其中的服務器ip
//將套接字地址與所創建的套接字號聯系起來。並檢測是否綁定成功
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("bind");
listen(sockSer, QUEUE_SIZE); //監聽端口隊列是否由連接請求,如果有就將該端口設置位可連接狀態,等待服務器接收連接
printf("Server Wait Client Accept......\n");
//如果監聽到有連接請求接受連接請求。並檢測是否連接成功,成功返回0,否則返回-1
int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
if(sockConn == -1)
perror("accept");
else
{
printf("Server Accept Client OK.\n");
printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
}
char sendbuf[256]; //申請一個發送緩存區
char recvbuf[256]; //申請一個接收緩存區
while(1)
{
printf("Ser:>");
scanf("%s",sendbuf);
if(strncmp(sendbuf,"quit",4) == 0) //如果所要發送的數據為"quit",則直接退出。
break;
send(sockConn, sendbuf, strlen(sendbuf)+1, 0); //發送數據
recv(sockConn, recvbuf, 256, 0); //接收客戶端發送的數據
printf("Cli:> %s\n",recvbuf);
}
close(sockSer); //關閉套接字
return 0;
}
cli.cpp:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define SERVER_PORT 5050
#define SERVER_IP "192.168.3.254"
int main(int argc, char *argv[])
{
//創建客戶端套接字號,並檢測是否創建成功
int sockCli;
sockCli = socket(AF_INET, SOCK_STREAM, 0);
if(sockCli == -1)
perror("socket");
//創建一個地址信息結構體,並對其內容進行設置
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET; //使用AF_INET協議族
addrSer.sin_port = htons(SERVER_PORT); //設置端口號
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); //設置服務器ip
bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr)); //將套接字地址與所創建的套接字號聯系起來
//創建一個與服務器的連接,並檢測連接是否成功
socklen_t addrlen = sizeof(struct sockaddr);
int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("connect");
else
printf("Client Connect Server OK.\n");
char sendbuf[256]; //申請一個發送數據緩存區
char recvbuf[256]; //申請一個接收數據緩存區
while(1)
{
recv(sockCli, recvbuf, 256, 0); //接收來自服務器的數據
printf("Ser:> %s\n",recvbuf);
printf("Cli:>");
scanf("%s",sendbuf);
if(strncmp(sendbuf,"quit", 4) == 0) //如果客戶端發送的數據為"quit",則退出。
break;
send(sockCli, sendbuf, strlen(sendbuf)+1, 0); //發送數據
}
close(sockCli); //關閉套接字
return 0;
}
運行結果:
UDP實現客戶端—服務器通信:
相對與TCP來說,UDP安全性差,面向無鏈接。所以UDP地實現少了連接與接收連接的操作。所以在收發數據時就不能再用send()和recvfrom()了,而是用sendto()和recvto()之名從哪收發數據。
sendto():
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
參數1(sockfd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(buf):指向存有發送數據的緩沖區的指針
參數3(len):緩沖區長度。
參數4(flags):flags的值或為0,或由下面常值中選擇一個:
MSGD_DONTROUTE:不查路由表 (send可選)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可選)
MSG_OOB : 發送或接收帶外數據。 (send、recv均可選)
MSG_PEEK : 查看外來的消息 (recv可選)
MSG_WAITALL : 等待所有數據 (recv可選)
參數5(dest_addr):指向目的地址的指針。
參數6(addrlen) : 目的地址的長度。
recvfrom():
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
參數1(sockfd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(buf):指向存有接收數據的緩沖區的指針
參數3(len):緩沖區長度。
參數4(flags):flags的值或為0,或由下面常值中選擇一個:
MSGD_DONTROUTE:不查路由表 (send可選)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可選)
MSG_OOB : 發送或接收帶外數據。 (send、recv均可選)
MSG_PEEK : 查看外來的消息 (recv可選)
MSG_WAITALL : 等待所有數據 (recv可選)
參數5(src_addr):指向源地址的指針。
參數6(addrlen) : 源地址的長度。
程序代碼:
ser.cpp:
#include
#include
#include
#include
#include
#include
int main()
{
//創建一個套接字,並檢測是否創建成功
int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
if(sockSer == -1)
perror("socket");
struct sockaddr_in addrSer; //創建一個記錄地址信息的結構體
addrSer.sin_family = AF_INET; //使用AF_INET協議族
addrSer.sin_port = htons(5050); //設置地址結構體中的端口號
addrSer.sin_addr.s_addr = inet_addr("192.168.3.169"); //設置通信ip
//將套接字地址與所創建的套接字號聯系起來,並檢測是否綁定成功
socklen_t addrlen = sizeof(struct sockaddr);
int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
if(res == -1)
perror("bind");
char sendbuf[256]; //申請一個發送數據緩存區
char recvbuf[256]; //申請一個接收數據緩存區
struct sockaddr_in addrCli;
while(1)
{
recvfrom(sockSer,recvbuf,256,0,(struct sockaddr*)&addrCli, &addrlen); //從指定地址接收客戶端數據
printf("Cli:>%s\n",recvbuf);
printf("Ser:>");
scanf("%s",sendbuf);
sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen); //向客戶端發送數據
}
return 0;
}
cli.cpp:
#include
#include
#include
#include
#include
#include
int main()
{
//創建一個套接字,並檢測是否創建成功
int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
if(sockCli == -1){
perror("socket");
}
addrSer.sin_family = AF_INET; //使用AF_INET協議族
addrSer.sin_port = htons(5050); //設置地址結構體中的端口號
addrSer.sin_addr.s_addr = inet_addr("192.168.3.169"); //設置通信ip
socklen_t addrlen = sizeof(struct sockaddr);
char sendbuf[256]; //申請一個發送數據緩存區
char recvbuf[256]; //申請一個接收數據緩存區
while(1){
//向客戶端發送數據
printf("Cli:>");
scanf("%s",sendbuf);
sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);
接收來自客戶端的數據
recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
printf("Ser:>%s\n", recvbuf);
}
return 0;
}
運行結果:
UDP的池先中沒有對客戶端與服務器所發送數據是否為“quit”進行判斷。判斷方法和TCP中的相同。可以將其完善。