”阻塞“,”非阻塞“,”同步“,”异步“。如果你写过 Socket 的网络程序,你一定在搜索资料的时候看到过这些名词。这些名词是什么意思?为什么在进行 Socket 编程的时候要引入这些概念?这些概念会带为我们的程序带来哪些优势?本期,我将会为大家一一讲解这些概念。相信学习完这些知识,你就能写出高效的 Socket 程序。
我们先来看一下最传统的 Socket 程序应该如何编写。以 TCP 为例,我们将写两个程序,一个程序作为 Server,一个程序作为 Client。
作为 TCP Server,程序流程如下。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock < 0) {
printf("socket() error");
return -1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(1234);
local.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(local);
if(bind(server_sock, (struct sockaddr*)&local ,len) < 0) {
printf("bind() error");
return -1;
}
if(listen(server_sock, 5) < 0) {
printf("listen() error");
return -1;
}
struct sockaddr_in remote;
socklen_t socketLen = sizeof(struct sockaddr_in);
while(1)
{
int sock = accept(server_sock, (struct sockaddr*)&remote, &socketLen);
while(1){
int dataLen = 32;
unsigned char data[dataLen];
int recvedDataLen = 0;
while(recvedDataLen < dataLen){
int ret = recv(sock, data + recvedDataLen, dataLen - recvedDataLen, 0);
recvedDataLen += ret;
}
int sendedDataLen = 0;
while(sendedDataLen < dataLen){
int ret = send(sock, data + sendedDataLen, dataLen - sendedDataLen, 0);
sendedDataLen += ret;
}
}
}
close(server_sock);
return 0;
}
在这段代码中,当有客户端连接之后,我们的程序会进入这个部分。
int dataLen = 32;
unsigned char data[dataLen];
int recvedDataLen = 0;
while(recvedDataLen < dataLen){
int ret = recv(sock, data + recvedDataLen, dataLen - recvedDataLen, 0);
recvedDataLen += ret;
}
int sendedDataLen = 0;
while(sendedDataLen < dataLen){
int ret = send(sock, data + sendedDataLen, dataLen - sendedDataLen, 0);
sendedDataLen += ret;
}
这部分的代码中,我们的 Server 会先接收 32 个字节长度的数据,然后将这 32 个字节的数据再发回到 Client 端。
接下来,我们再来编写 Client 的代码
作为 TCP Client,程序流程如下。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <time.h>
#include <chrono>
#include <thread>
int main()
{
int SERVER_PORT = 1234;
const char * SERVER_ADDR = "127.0.0.1";
struct sockaddr_in serverAddr;
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
serverAddr.sin_port = htons(SERVER_PORT);
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
int ret = connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
if (ret < 0) {
printf("连接失败\n");
close(clientSocket);
return -1;
}
while(1){
int dataLen = 32;
unsigned char data[] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F'
};
int sendedDataLen = 0;
while(sendedDataLen < dataLen){
int ret = send(clientSocket, data + sendedDataLen, dataLen - sendedDataLen, 0);
printf("ret: %d\n", ret);
sendedDataLen += ret;
}
int recvedDataLen = 0;
while(recvedDataLen < dataLen){
int ret = recv(clientSocket, data + recvedDataLen, dataLen - recvedDataLen, 0);
printf("ret: %d\n", ret);
recvedDataLen += ret;
}
for(int i=0;i<dataLen;i++){
printf("%c", data[i]);
}
printf("\n");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
在这端段程序中,我们让 Client 去连接 Server 端,连接成功之后,会先发送 32 字节的数据出去,然后再尝试接收 32 个字节的数据,之后休眠 1 秒,再循环发送 32 字节数据。这正好和服务端的程序相互配合。
(代码仓库)