UDP程式設計步驟概述
1. 概述
UDP提供的是無連線、不可靠的資料報服務。
在傳輸過程中資料可能會丟失。我們只有通過在應用層進行正確的控制才能修復在傳輸層上存在的缺陷。因此,需要我們編寫可靠的UDP應用程式。
UDP客戶端與伺服器互動的步驟如圖:
使用UDP協議進行通訊時,客戶端並不需要與伺服器建立連線,只需要通過sendto系統呼叫給指定的伺服器傳送資料報。相同地,伺服器也不需要監聽或接收客戶端的連線,只需要通過recvfrom系統呼叫等待客戶端傳送資料報。
sendto函式與recvfrom函式都是阻塞的!
2. UDP應用程式的編寫步驟
基本步驟如下:
- 建立套接字
- 命名套接字(伺服器端)
- 伺服器端,等待客戶端發來資料
- 客戶端,向伺服器端傳送資料報
- 關閉套接字
步驟一:建立socket
使用系統呼叫socket建立一個套接字並返回這個套接字的檔案描述符sockfd
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
一個套接字為一條通訊線路的一個端點。
domain
domain引數指定哪種協議族,常見的協議族有 AF_INET、AF_INET6 和 AF_UNIX 。AF_UNIX 用於通過檔案系統實現的本地套接字,AF_INET 用於網路套接字。
type type
引數指定服務型別,即 SOCK_STREAM 和 SOCK_DGRAM。
SOCK_STREAM 為流套接字,基於 TCP,提供可靠,有序的服務。
SOCK_DGRAM 為資料報套接字,基於 UDP,提供不可靠,無序的服務。
protocol
prorocol引數指定具體的協議,通常前兩個引數就可以確定具體的協議,所以我們把這個引數預設設定為0。
步驟二:命名socket
要想讓建立的套接字可以被其他程序使用,那必須給該套接字命名。對套接字命名的意思是指將該套接字關聯一個IP地址和埠號,使用系統呼叫bind
來實現。
#include <sys/socket.h> int bind(int socket, const struct sockaddr *address, size_t address_len);
bind
系統呼叫把引數address
中的地址分配給與檔案描述符socket
關聯的套接字,address_len為address
地址結構的長度。
每種套接字都有自己的地址格式,對於AF_INET地址族,套接字地址由socket_in來指定。
struct sockaddr_in
{
sa_family_t sin_family; //協議族
u_int16_t sin_port; //埠號
struct in_addr sin_addr; //IP地址
};
struct in_addr
{
u_int32_t s_addr;
};
對於客戶套接字,我們一般不需要指定套接字的埠號,而對於伺服器套接字,我們需要指定套接字的埠號以便讓客戶正確向伺服器傳送資料。
步驟三:伺服器接收客戶端訊息
UDP 是無連線不可靠的資料報協議。伺服器不接收來自客戶的連線,而只管呼叫recvfrom
系統呼叫,等待客戶的資料到達。recvfrom
的宣告如下:
#include <sys/socket.h>
int recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *src_len);
recvfrom的引數說明:
- socket:建立的套接字描述符
- buffer:指向輸入緩衝區的指標
- length:緩衝區大小
- flags:在本文中,可以將 flags 置為0即可
- src_addr:指向客戶套接字地址的指標
- src_len:地址長度recvfrom的返回值為讀入資料的長度。
步驟四:客戶端向伺服器傳送訊息
UDP是無連線的,客戶端可以直接向伺服器傳送訊息而不需要建立連線。使用sendto
系統呼叫向伺服器傳送訊息。函式定義如下:
#include <sys/socket.h>
int sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
sendto
函式的引數說明:
-socket:建立的套接字描述符
-buffer:輸出緩衝區的指標
-length:緩衝區大小
-flags:正常應用中,flags一般設定為0
-dest_addr:指向伺服器套接字地址的指標
-dest_len:地址長度
步驟五:關閉socket
作業系統為每個套接字分配了一個檔案描述符,使用完後需要關閉使作業系統回收該檔案描述符,使用 close
系統呼叫。
#include<sys/socket.h>
int close(int sockfd);
3. 程式碼段
伺服器端:
int main()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(sockfd != 0);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6500);
ser.sin_addr.s_addr = inet_addr("127.0.0.1"); //迴環地址
int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
while(1)
{
char recvbuf[128] = {0};
int len = sizeof(cli);
int n = recvfrom(sockfd, recvbuf, 127, 0, (struct sockaddr*)&cli, &len);
if(n <= 0)
{
printf("recvfrom error\n");
continue;
}
printf("recvbuff: %s\n", recvbuf);
sendto(sockfd, "OK", 2, 0, (struct sockaddr*)&cli, sizeof(cli));
}
close(sockfd);
}
客戶端:
int main(int argc, char *argv[])
{
if(argc <3)
{
printf("please choose server's IP && port\n");
exit(0);
}
int port = 0;
sscanf(argv[2], "%d", &port);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(sockfd != -1);
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_addr.s_addr = inet_addr(argv[1]);
ser.sin_port = htons(port);
while(1)
{
printf("please input data: ");
fflush(stdout);
char buff[128] = {0};
fgets(buff, 128, stdin);
buff[strlen(buff) - 1] = 0;
if(strncmp(buff, "end", 3) == 0)
{
close(sockfd);
break;
}
sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&ser, sizeof(ser));
memset(buff, 0, 128);
recvfrom(sockfd, buff, 127, 0, NULL, NULL);
printf("%s\n", buff);
}
close(sockfd);
}