一、概述
1)TCP客戶—服務器程序設計基本框架
TCP的三次握手與四次揮手(詳解+動圖)
-
UDP客戶—服務器程序設計基本框架流程圖
-
UDP和TCP的對比:
從上面的流程圖比較我們可以很明顯的看出UDP沒有三次握手過程。
簡單點說。UDP處理的細節比TCP少。UDP不能保證消息被傳送到(它也報告消息沒有傳送到)目的地。UDP也不保證數據包的傳送順序。UDP把數據發出去后只能希望它能夠抵達目的地。
TCP優缺點:
優點:
1.TCP提供以認可的方式顯式地創建和終止連接。
2.TCP保證可靠的、順序的(數據包以發送的順序接收)以及不會重復的數據傳輸。
3.TCP處理流控制。
4.允許數據優先
5.如果數據沒有傳送到,則TCP套接口返回一個出錯狀態條件。
6.TCP通過保持連續並將數據塊分成更小的分片來處理大數據塊。—無需程序員知道
缺點: TCP在轉移數據時必須創建(並保持)一個連接。這個連接給通信進程增加了開銷,讓它比UDP速度要慢。
UDP優缺點:
1.UDP不要求保持一個連接
2.UDP沒有因接收方認可收到數據包(或者當數據包沒有正確抵達而自動重傳)而帶來的開銷。
3.設計UDP的目的是用於短應用和控制消息
4.在一個數據包連接一個數據包的基礎上,UDP要求的網絡帶寬比TDP更小。
二、Socket編程
Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,程序員可以用它們來開發TCP/IP網絡上的應用程序。要學Internet上的TCP/IP網絡編程,必須理解Socket接口。
Socket接口設計者先是將接口放在Unix操作系統里面的。如果了解Unix系統的輸入和輸出的話,就很容易了解Socket了。網絡的Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個類似於打開文件的函數調用Socket(),該函數返回一個整型的Socket描述符,隨后的連接建立、數據傳輸等操作都是通過該Socket實現的。常用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對於面向連接的TCP服務應用;數據報式Socket是一種無連接的Socket,對應於無連接的UDP服務應用。
1、socket調用庫函數主要有:
創建套接字
int Socket(int af,int type,int protocol) --------------> socket(ip地址類型,數據傳輸方式,傳輸協議)
- af 為地址族(Address Family),也就是 IP 地址類型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的簡寫,INET是“Inetnet”的簡寫。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
127.0.0.1,它是一個特殊IP地址,表示本機地址,后面的教程會經常用到。
你也可以使用 PF 前綴,PF 是“Protocol Family”的簡寫,它和 AF 是一樣的。例如,PF_INET 等價於 AF_INET,PF_INET6 等價於 AF_INET6。
2) type 為數據傳輸方式/套接字類型,常用的有 SOCK_STREAM(流格式套接字/面向連接的套接字) 和 SOCK_DGRAM(數據報套接字/無連接的套接字),我們已經在《套接字有哪些類型》一節中進行了介紹。
- protocol 表示傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議。
建立地址和套接字的聯系 (服務器端要用 bind() 函數將套接字與特定的 IP 地址和端口綁定起來,只有這樣,流經該 IP 地址和端口的數據才能交給套接字處理)
int bind(int sock, struct sockaddr *addr, socklen_t addrlen) -------------->bind(套接字標識符,結構體指針,結構體變量的大小)
sock 為 socket 文件描述符,addr 為 sockaddr 結構體變量的指針,addrlen 為 addr 變量的大小,可由 sizeof() 計算得出。
//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//創建sockaddr_in結構體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
serv_addr.sin_port = htons(1234); //端口
//將套接字和IP、端口綁定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
服務器端偵聽客戶端的請求
對於服務器端程序,使用 bind() 綁定套接字后,還需要使用 listen() 函數讓套接字進入被動監聽狀態,再調用 accept() 函數,就可以隨時響應客戶端的請求了。
listen( Sockid ,quenlen) --------------> listen( 套接字標識符 ,請求隊列的長度)
sock 為需要進入監聽狀態的套接字,backlog 為請求隊列的最大長度。
建立服務器/客戶端的連接 (面向連接TCP)
客戶端請求連接
Connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen) -------------->Connect(套接字標識符,結構體指針,結構體變量的大小)
當套接字處於監聽狀態時,可以通過 accept() 函數來接收客戶端請求。
newsockid=accept(Sockid,Clientaddr, paddrlen) --------------> accept(套接字標識符,客戶端的結構體指針變量, 結構體變量長度)
它的參數與 listen() 和 connect() 是相同的:sock 為服務器端套接字,addr 為 sockaddr_in 結構體變量,addrlen 為參數 addr 的長度,可由 sizeof() 求得。
accept() 返回一個新的套接字來和客戶端通信,addr 保存了客戶端的IP地址和端口號,而 sock 是服務器端的套接字,大家注意區分。后面和客戶端通信時,要使用這個新生成的套接字,而不是原來服務器端的套接字。
最后需要說明的是:listen() 只是讓套接字進入監聽狀態,並沒有真正接收客戶端請求,listen() 后面的代碼會繼續執行,直到遇到 accept()。accept() 會阻塞程序執行(后面代碼不能被執行),直到有新的請求到來。
發送/接收數據
面向連接:send(sockid, buff, bufflen)
recv( )
面向無連接:sendto(sockid,buff,…,addrlen)
recvfrom( )
send()/recv()和write()/read():發送數據和接收數據
釋放套接字
close(sockid)
基於socket的服務端與客戶端之間的相互通信:
服務端的代碼:
/* socket_tcp_server.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h> ///errno的頭文件,不能用errno.h
#define MAX_MSG_SIZE 256
int main(int argc, char const *argv[])
{
/* code */
int server_socket_fd;//服務端套接字標識符
int client_socket_fd;
struct sockaddr_in server_addr;//服務端的信息
struct sockaddr_in client_addr;//客戶端的信息
char msg[MAX_MSG_SIZE] = {0};//消息緩沖區
char sendmsg[MAX_MSG_SIZE] = {0};//發送數據
/* 創建套節字 */
server_socket_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (server_socket_fd < 0) {
fprintf(stderr,"create socket ERROR:%s\n",strerror(errno));
exit(1);
}
/* 將套接字和IP、端口綁定 */
memset(&server_addr, 0, sizeof(server_addr)); //每個字節都用0初始化
server_addr.sin_family = AF_INET;//使用ipv4地址
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
server_addr.sin_port = htons(4321); //端口
bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
/* 創建套接字監聽隊列 */
if (listen(server_socket_fd,10) <0 ) {//監聽隊列最大的長度為10
fprintf(stderr,"listen ERROR:%s\n",strerror(errno));
close(server_socket_fd);
exit(1);
}
socklen_t client_addr_size = sizeof(client_addr);//客戶端信息的長度
/* 接受客戶請求 */
client_socket_fd = accept(server_socket_fd,(struct sockaddr*)&client_addr,&client_addr_size);
if (client_socket_fd <= 0) {
fprintf(stderr,"accept ERROR:%s\n",strerror(errno));
}else {
while (1)
{
/* 接收到客戶端的請求后,服務端開始為客戶端服務 */
/* 傳數據到客戶端 */
//strcpy(msg,"你好啊,客戶端老弟,我是服務端渣渣輝啊!!");
printf("請輸入你要傳遞給客戶端的信息:\n");
gets(sendmsg);
//write(client_socket_fd,sendmsg,MAX_MSG_SIZE);
send(client_socket_fd,sendmsg,MAX_MSG_SIZE,0);
memset(sendmsg,0,MAX_MSG_SIZE);
printf("我是服務端小賤賤:正在為您服務ing!!!!\n");
//int read_count = read(client_socket_fd,msg,MAX_MSG_SIZE);
int read_count = recv(client_socket_fd,msg,MAX_MSG_SIZE,0);
if (read_count > 0) {
printf("received a connection from %s\n", inet_ntoa(client_addr.sin_addr));
printf("the message is :%s from the client\n",msg);
memset(msg,0,MAX_MSG_SIZE);
}
}
}
/* 關閉套接字 */
close(client_socket_fd);
close(server_socket_fd);
return 0;
}
客戶端的代碼:
/* socket_tcp_client.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DATA_MAX_SIZE 256
int main(int argc, char const *argv[])
{
/* code */
int client_socket_fd;
int result;
struct sockaddr_in server_addr;//服務端的信息
/* 創建套節字 */
client_socket_fd = socket(AF_INET,SOCK_STREAM,0);
/* 將套接字和IP、端口綁定 */
memset(&server_addr, 0, sizeof(server_addr)); //每個字節都用0初始化
server_addr.sin_family = AF_INET;//使用ipv4地址
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
server_addr.sin_port = htons(4321); //端口
result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(result == -1)
{
perror("ops:client\n");
exit(1);
}
/* 讀取服務器數據 */
char data[DATA_MAX_SIZE] = {0};//接受數據
char sendmsg[DATA_MAX_SIZE] = {0};//發送數據
while (1)
{
/* 接受數據 */
//int read_count = read(client_socket_fd,data,DATA_MAX_SIZE);
int read_count = recv(client_socket_fd,data,DATA_MAX_SIZE,0);
if (read_count > 0 ) {
printf("the message is :%s from the server\n",data);
}
/* 發送數據 */
printf("請輸入你要發給服務端的數據:\n");
gets(sendmsg);
//sendmsg[DATA_MAX_SIZE-1] = '\0';
//write(client_socket_fd,sendmsg,DATA_MAX_SIZE);
send(client_socket_fd,sendmsg,DATA_MAX_SIZE,0);
}
/* 關閉套接字 */
close(client_socket_fd);
return 0;
}