文章目錄
- 一、TCP傳輸
- 二、相關接口
- 三、多進程版本
- 四、多線程版本
一、TCP傳輸
TCP和UDP類似,但是在傳輸中TCP有輸入,輸出緩沖區
,看下面的傳輸圖片
可以理解為TCP之間的數據傳輸都是依賴各自的socket,socket就充當傳輸的中介吧。
而每個socket都對應兩個緩沖區,一個輸入緩沖區,一個輸出緩沖區 。
二、相關接口
下?介紹程序中?到的socketAPI,這些函數都在sys/socket.h中。
socket函數
的聲明:
int socket(int domain, int type, int protocol);
功能:打開網絡文件(套接字)。
-
參數domain:確定IP地址類型,如IPv4還是IPv6。
AF_INET: IPv4。AF_INET6:IPv6。
-
參數type:確定數據的傳輸方式。
SOCK_STREAM: 流式套接字(TCP)。SOCK_DGRAM: 數據報套接字(UDP)。
-
參數protocol:確定協議類型,如果前面type已經能確定了,這里傳入0即可。
-
返回值:
成功:文件描述符。
失敗:一個小于0的數。
bind函數
的使用
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用來綁定端口。
- 參數sockfd:要綁定的套接字描述符(由 socket() 函數創建)。
- 參數sockaddr:指向地址結構體的指針,包含綁定的IP地址和端口號。
- 參數addrlen:地址結構體的長度(單位:字節)。
- 返回值:
0:成功。
非0:失敗
listen函數
的使用
int listen(int sockfd, int backlog);
功能:將主動套接字(SOCK_STREAM)轉換為被動監聽狀態,等待客戶端連接請求。
- 參數sockfd: 已綁定(bind)但未連接的套接字描述符
- 參數backlog:等待連接隊列的最大長度
listen()聲明sockfd處于監聽狀態,并且最多允許有backlog個客?端處于連接等待狀態,如果接收
到更多的連接請求就忽略 - 返回值:
0:成功。
非0:失敗
accept函數
的使用
int accept(int sockfd, struct sockaddr * addr,socklen_t * addrlen);
功能:三次握?完成后,服務器調?accept()接受連接;
如果服務器調?accept()時還沒有客?端的連接請求,就阻塞等待直到有客?端連接上來
- 參數sockfd:處于監聽狀態(LISTEN)的套接字描述符
- 參數addr:addr是?個輸出型參數,accept()返回時傳出客?端的地址和端?號;
如果給addr參數傳NULL,表?不關?客?端的地址;
參數addrlen:是?個傳?傳出參數(value-result argument),傳?的是調?者提供的,緩沖區addr
的?度以避免緩沖區溢出問題,傳出的是客?端地址結構體的實際?度(有可能沒有占滿調?者提
供的緩沖區); - 返回值
-≥0 成功,返回新套接字描述符(與客戶端通信用)
-1 失敗,錯誤碼存儲在errno中
connect函數
的使用
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:客?端需要調?connect()連接服務器;
-
connect和bind的參數形式?致,區別在于bind的參數是??的地址,?connect的參數是對?的
地址; -
成功返回0,出錯返回-1;
下面就來根據緩沖區來說說,send()/write() 與 recv()/read() 兩個函數吧。
注意: read(),write()是通用文件IO, recv(),send()是socket專用,在這里用哪一個都可以
send()/write()
ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
-
sockfd: socket套接字描述符
-
buf: 數據緩沖區
-
len: 要寫入的字節數
-
flags: 控制標志
ssize_t write(int fd, const void *buf, size_t count);
-
fd: 任意文件描述符
-
buf: 數據緩沖區
-
count: 要寫入的字節數
功能:都是用于數據發送或寫入的函數
記住 ,我們只需要知道send()/write() 是將數據包發送到緩沖區里就行了,至于是什么時候將緩沖區里的數據發送到接收端的socket就不用我們應用層來管了,實際上想管也管不到。
有兩種情況會將緩沖區里的數據全部發送出去
-
情況1:緩沖區滿了,很好理解吧,滿了當然得發走啊,不然哪里有新的空間放新數據?
-
情況2:隔到一定時間,發現沒有數據再繼續send到緩沖區里來了,即便沒有滿也會趕緊發過去,而這個時間是非常短的。
所以,不用擔心說數據發送不及時,而為什么要這樣,則是為了提高數據發送的效率了。
recv()/read()
ssize_t recv(int sockfd, void buf[.len], size_t len, int flags);
-
sockfd: socket套接字描述符
-
buf: 數據緩沖區
-
len: 緩沖區可用容量(字節數)
-
flags: 控制標志
ssize_t read(int fd, void *buf, size_t count);
-
fd: 任意文件描述符
-
buf: 數據緩沖區
-
count: 期望讀取的最大字節數
功能:都是用于接收或讀取數據的函數
socket不論是發送還是接收緩沖區就相當于一個管道啊,先進先出,讀取出來就不在管道里了。
簡單的通信代碼
client.cc
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>int main(int argc,char* argv[])
{if(argc != 3){std::cout << "Usage " << argv[0] << " ip port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock < 0){perror("socket fail");exit(1);}uint16_t port = std::stoi(argv[2]);std::string ip = argv[1];sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_family = AF_INET;sd.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &sd.sin_addr);int n = connect(sock, (const sockaddr*)&sd, sizeof(sd));if(n < 0){perror("connect fail");exit(1);}while(true){std::string message;std::cout << "Please Enter# ";std::getline(std::cin, message);ssize_t w = write(sock, message.c_str(), message.size());if(w <= 0){perror("write fail");break;}char buffer[4096];ssize_t r = read(sock, buffer, sizeof(buffer));if(r < 0){perror("read fail");break;}else if(r > 0){buffer[r] = '\0';std::cout << "接收服務端: " << buffer << std::endl;}else{break;}}close(sock);return 0;
}
server.cc
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>const int default_backlog = 4;int main(int argc,char* argv[])
{if(argc != 2){std::cout << "Usage "<< argv[0] << " port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock < 0){perror("socket fail");exit(1);}int opt = 1;setsockopt(sock, SOL_SOCKET,SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));uint16_t port = std::stoi(argv[1]);sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_addr.s_addr = INADDR_ANY;sd.sin_family = AF_INET;sd.sin_port = htons(port);int b = bind(sock, (const sockaddr*)&sd, sizeof(sd));if(b < 0){perror("bind fail");exit(1);}int list = listen(sock, default_backlog);if(list < 0){perror("listen fail");exit(1);}while(true){sockaddr_in sd;socklen_t len = sizeof(sd);int sockfd = accept(sock, (sockaddr*)&sd, &len);if(sockfd < 0){std::cout << "accept fail" << std::endl;continue;}std::cout << "接收成功 ip: " << inet_ntoa(sd.sin_addr) << " port: " << ntohs(sd.sin_port) << std::endl;while(true){char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if(n < 0){perror("read fail");break;}else if(n == 0){std::cout << "client quit" << std::endl;break;}else{buffer[n] = '\0';std::cout << "client say: " << buffer << std::endl;std::string echo = "server say: ";echo += buffer;ssize_t w = write(sockfd, echo.c_str(), echo.size());if(w <= 0)exit(1);}}close(sockfd);}close(sock);return 0;
}
測試多個連接的情況
再啟動?個客?端,嘗試連接服務器,發現第?個客?端,不能正確的和服務器進?通信
分析原因,是因為我們 accecpt 了?個請求之后,就在?直 while 循環嘗試 read ,沒有繼續調?
到 accecpt ,導致不能接受新的請求
我們當前的這個 TCP ,只能處理?個連接,這是不科學的,我們需要引入多進程或者多線程
三、多進程版本
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>#include <sys/wait.h>
#include <sys/types.h>const int default_backlog = 4;void Handler(int sockfd, const sockaddr_in& sd)
{std::cout << "接收成功 ip: " << inet_ntoa(sd.sin_addr) << " port: " << ntohs(sd.sin_port) << std::endl;while (true){char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){perror("read fail");break;}else if (n == 0){std::cout << "client quit" << std::endl;break;}else{buffer[n] = '\0';std::cout << "client say: " << buffer << std::endl;std::string echo = "server say: ";echo += buffer;ssize_t w = write(sockfd, echo.c_str(), echo.size());if (w <= 0)break;}}close(sockfd);exit(0);
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage " << argv[0] << " port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("socket fail");exit(1);}int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));uint16_t port = std::stoi(argv[1]);sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_addr.s_addr = INADDR_ANY;sd.sin_family = AF_INET;sd.sin_port = htons(port);int b = bind(sock, (const sockaddr *)&sd, sizeof(sd));if (b < 0){perror("bind fail");exit(1);}int list = listen(sock, default_backlog);if (list < 0){perror("listen fail");exit(1);}while (true){sockaddr_in sd;socklen_t len = sizeof(sd);int sockfd = accept(sock, (sockaddr *)&sd, &len);if (sockfd < 0){std::cout << "accept fail" << std::endl;continue;}pid_t id = fork();if(id < 0){close(sockfd);continue;}else if(id == 0){if(fork() > 0){close(sockfd);exit(0);}Handler(sockfd, sd);}else{close(sockfd);waitpid(id, nullptr, 0);}}close(sock);return 0;
}
四、多線程版本
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>#include <sys/wait.h>
#include <sys/types.h>#include <pthread.h>const int default_backlog = 4;struct Arg
{Arg(int sock, sockaddr_in s): sockfd(sock), sd(s){}int sockfd;sockaddr_in sd;
};void* Handler(void* arg)
{Arg* a = static_cast<Arg*>(arg);int sockfd = a->sockfd;sockaddr_in sd = a->sd;delete a;std::cout << "接收成功 ip: " << inet_ntoa(sd.sin_addr) << " port: " << ntohs(sd.sin_port) << std::endl;while (true){char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){perror("read fail");break;}else if (n == 0){std::cout << "client quit" << std::endl;break;}else{buffer[n] = '\0';std::cout << "client say: " << buffer << std::endl;std::string echo = "server say: ";echo += buffer;ssize_t w = write(sockfd, echo.c_str(), echo.size());if (w <= 0)break;}}close(sockfd);return nullptr;
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage " << argv[0] << " port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("socket fail");exit(1);}int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));uint16_t port = std::stoi(argv[1]);sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_addr.s_addr = INADDR_ANY;sd.sin_family = AF_INET;sd.sin_port = htons(port);int b = bind(sock, (const sockaddr *)&sd, sizeof(sd));if (b < 0){perror("bind fail");exit(1);}int list = listen(sock, default_backlog);if (list < 0){perror("listen fail");exit(1);}while (true){sockaddr_in sd;socklen_t len = sizeof(sd);int sockfd = accept(sock, (sockaddr *)&sd, &len);if (sockfd < 0){std::cout << "accept fail" << std::endl;continue;}Arg* a = new Arg(sockfd, sd);pthread_t id;int n = pthread_create(&id, nullptr, Handler, (void*)a);if(n < 0){close(sockfd);delete a;continue;}pthread_detach(id);}close(sock);return 0;
}