远古socket通信
一 前言
在计算机网络诞生不久的那个年代,计算机操作系统是单任务的,windows对应的是dos系统,一个时刻只允许运行一个进程,那时候的socket通信不存在并发的概念,就是单纯的串行socket通信。一个节点发送数据,一个节点接收数据。
基本的socket通信建立流程如下:
二 服务端
2.1 服务端代码编写
建立socket通信的步骤也是大多数教材的范例,以下是一个服务端的代码示例,我们可以简单回顾一下:
//server1.cpp
#include<unistd.h>
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<signal.h>
const uint16_t recv_buff_len = 1024;
const uint16_t port = 8880;
int main(int argc, char const *argv[])
{
//创建TCP套接字文件描述符,AF_INET是指ipv4地址类型,SOCK_STREAM指流式套接字,0指流式套接字的默认协议TCP。
int fd = socket(AF_INET,SOCK_STREAM,0);
if (fd == -1)
{
printf("create socket error!\n");
return -1;
}
//因为第一步创建了ipv4地址类型的套接字文件描述符,所以选择sockaddr_in结构体,如果是ipv6选sockaddr_in6结构体,如果是本地通信文件地址类型,选sockaddr_un结构体。
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定地址和端口。
int ret = bind(fd,(struct sockaddr*)&serv_addr,sizeof(sockaddr));
if (ret == -1)
{
std::cout << "bind addr error!" << std::endl;
return -1;
}
//开始监听,最大连接数为1024
if(listen(fd,1024) == -1){
std::cout << "listen addr error!" << std::endl;
return -1;
}
//用c++11的string构造发送缓冲区和接收缓冲区,string字符串的内存为连续的。&recv_buff.at(0)表示第一个元素的地址,即接收缓存的头指针。recv_buff.size()表示这段缓存的数据长度。
std::string send_buf = R"({"radio": {"proto": "11axg","country": "Britain","bandwidth": "HT20","usrlimit": "32","chanid": "auto","bswitch": "1"}})";
std::string recv_buff = "";
recv_buff.resize(recv_buff_len);
//主循环接收新连接,同时接收连接上的数据。
while (1){
int conn_fd = -1;
conn_fd = accept(fd,(struct sockaddr*)NULL,NULL);
if(-1 == conn_fd){
std::cout << "connect error!" << std::endl;
continue;
}else{
std::cout << "accept a new connet client!" << std::endl;
}
//收发这条连接上的数据,直到客户端断开这条连接,服务端才能跳出循环,为后面客户端提供服务。
while (1){
int recv_len = recv(conn_fd, &recv_buff.at(0),recv_buff.size(),0);
if(recv_len == -1){
std::cout << "recv data error!" << std::endl;
close(conn_fd);
break;
}else if(recv_len == 0){
std::cout << " client socket closed!" << std::endl;
close(conn_fd);
break;
}
std::cout << "recive client data : " << recv_buff << std::endl;
int send_len = send(conn_fd,&send_buf.at(0),send_buf.size(),0);
if(send_len == -1){
std::cout << "send data error!" << std::endl;
close(conn_fd);
break;
}
}
}
close(fd);
std::cout <<"quit server!" <<std::endl;
return 0;
}
这种服务器是串行服务的,一次只能连接一个客户端,其他客户端需要排队等候,前面客户端断开连接后,服务端才能提供给新客户端提供连接,示意图如下:
2.2 服务端代码编译
g++ server1.cpp -o server1
代码编译运行环境为
ubuntu20.04 server
cat /proc/version Linux version 5.4.0-107-generic (buildd@lcy02-amd64-058) (gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)) #121-Ubuntu SMP Thu Mar 24 16:04:27 UTC 2022
2.3 服务端运行
运行服务器:
./server1
三 客户端
以下是一个客户端的代码示例:
//client1.cpp
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string>
#include<signal.h>
const uint16_t recv_buf_len = 1024;
const uint16_t port = 8880;
bool bLoop = true;
void handler(int sig){
bLoop = false;
}
int main(int argc, char const *argv[])
{
if(argc < 2){
std::cout << "usage: ./client1 <ip address>" << std::endl;
return -1;
}
//创建TCP套接字文件描述符,AF_INET是指ipv4地址类型,SOCK_STREAM指流式套接字,0指流式套接字的默认协议TCP。
int fd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == fd){
std::cout << "socket create error!" << std::endl;
return -1;
}
//因为第一步创建了ipv4地址类型的套接字文件描述符,所以选择sockaddr_in结构体,如果是ipv6选sockaddr_in6结构体,如果是本地通信文件地址类型,选sockaddr_un结构体。
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(fd,(struct sockaddr*)&servaddr,sizeof(servaddr));
if(-1 == ret){
std::cout << "connet server error!" << std::endl;
close(fd);
return -1;
}
//用c++11的string构造发送缓冲区和接收缓冲区,string字符串的内存为连续的。&recv_buff.at(0)表示第一个元素的地址,即接收缓存的头指针。recv_buff.size()表示这段缓存的数据长度。
std::string send_buf = "client1 keep alived.";
std::string recv_buff = "";
recv_buff.resize(recv_buf_len);
//注册linux软中断信号SIGINT,SIGINT信号由就键盘Ctrl+C产生,handler出发后的处理函数。目的是希望客户端能ctrl+c后优雅的退出。
signal(SIGINT,handler);
while (bLoop){
int send_len = send(fd,&send_buf.at(0),send_buf.size(),0);
if(send_len == -1){
std::cout << "send data error!" << std::endl;
break;
}
sleep(0.5);
int recv_len = recv(fd,&recv_buff.at(0),recv_buff.size(),0);
if(recv_len == -1){
std::cout << "receive error!" << std::endl;
break;
}else if(recv_len == 0){
std::cout << " socket closed!" << std::endl;
break;
}
std::cout << " receive server data: " << recv_buff << std::endl;
}
std::cout << "client1 quit!" <<std::endl;
close(fd);
return 0;
}
编译客户端代码:
g++ client1.cpp -o client1
启动客户端:
./client1 192.168.49.128