5-3 网络编程—编写服务端与客户端demo
1. 编写简易客户端与服务端程序
1.1 服务端构建流程
如上图所示,使用Socket API建立简易的TCP服务端过程为:
-
- 建立一个socket()
-
- 绑定接受客户端连接的端口 bind()
-
- 监听网络端口 listen()
-
- 等待接受客户端连接 accept()
-
- 向客户端发送一条数据 send()
-
- 接收客户端发送的数据 recv()
-
- 关闭socket()
1.2 服务端源码
//
// Created by Evila on 2021/6/27.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<iostream>
using namespace std;
int main(int argc, char** argv)
{
// 1. 建立一个Socket
// 参数 ipv4 面向字节流的 tcp协议
int _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if( _sock < 0)
{
cout << "create socket error: " << strerror(errno) << "%s(errno: " << errno << endl;
exit(0);
}
// 2. 绑定端口45678
sockaddr_in _sin = {}; // sockaddr_in为网络地址的结构体
_sin.sin_family = AF_INET; // 设置协议类型
int _port = 45678;
_sin.sin_port = htons(_port); // 设置协议源端口号
// 计算机数据表示存在两种字节顺序:
// 网络字节顺序(Network Byte Order, NBO)与主机字节顺序(Host Byte Order, HBO)
// NBO是大端模式(big-endian),也就是整数的高位字节存放在内存的低地址处
// 在网络上使用统一的网络字节顺序,可以避免兼容性问题
// 而主机字节序与CPU或操作系统相关, 无法统一; 因此使用htons()将主机字节序转换成网络字节序
_sin.sin_addr.s_addr = htonl(INADDR_ANY); // 协议源地址 随机ip
if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == -1) //sockaddr 不利于编码
{
cout << "ERROR: 绑定用于接受客户端连接的网络端口失败..." << endl;
exit(0);
}
else
{
cout << "SUCCESS: 绑定端口" << _port << "成功..." << endl;
}
// 3. 监听网络端口 listen
if (listen(_sock, 5) == -1) // 第二个参数 backbag 为连接为完成队列长度
{
cout << "ERROR: 监听用于接受客户端连接的网络端口失败..." << endl;
exit(0);
}
else
{
cout << "SUCCESS: 监听端口成功..." << endl;
}
// 4. 等待接受客户端连接 accept
sockaddr_in _clientAddr = {};
int cliendAddrLen = sizeof(_clientAddr);
int _clientSock = -1; // 初始化无效的socket 用来存储接入的客户端
char msgBuf[] = "Hello, I'm Server";
char recvBuff[2048];
// 这里为了方便测试 只接受10次连接就关闭
int n = 10;
while (n--)
{
// 当客户端接入时 accept函数会得到客户端的socket地址和长度
_clientSock = accept(_sock, (sockaddr*)&_clientAddr, (socklen_t *)&cliendAddrLen);
if (-1 == _clientSock) //接受到无效接入
{
cout << "ERROR: 接受到无效客户端SOCKET..." << endl;
continue;
}
else
{
//inet_ntoa 将ip地址转换成可读的字符串
cout << "新Client加入: IP = " << inet_ntoa(_clientAddr.sin_addr) << endl;
// 5. 向客户端发送数据 send()
send(_clientSock, msgBuf, strlen(msgBuf) + 1, 0); // +1是为了把\0算进去
}
int recvLen = recv(_clientSock, recvBuff, 2048, 0);
recvBuff[recvLen] = '\0'; // 设置字符串结束符
cout << "recv msg from client: " << recvBuff << endl;
}
// 7. 关闭socket
close(_sock);
return 0;
}
1.3 编译并运行
使用g++命令编译并生成可执行文件server,执行后该进程阻塞在accept()函数处。
1.4 客户端源码
//
// Created by Evila on 2021/6/27.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char** argv)
{
// 1. 建立一个Socket
int _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_sock < 0)
{
cout << "create socket error: " << strerror(errno) << "%s(errno: " << errno << endl;
exit(0);
}
// 2. 设置请求连接的server 地址
sockaddr_in _sevrAddr = {}; // sockaddr_in为网络地址的结构体
_sevrAddr.sin_family = AF_INET;
int port = 45678;
_sevrAddr.sin_port = htons(port);
string server_ip;
cout << "please input server ip: ";
cin >> server_ip;
if (inet_pton(AF_INET, server_ip.c_str(), &_sevrAddr.sin_addr) <= 0)
{
cout <<"inet_pton error for " << server_ip << endl;
exit(0);
}
// 3. 连接服务端
int ret = connect(_sock, (struct sockaddr*)&_sevrAddr, sizeof(_sevrAddr));
if (ret < 0)
{
cout << "connect error: " << strerror(errno) << "errno: " << errno << endl;
exit(0);
}
else
{
cout << "connect to server: " << server_ip << ":" << port << " success!" << endl;
}
// 4. 向服务端发送数据
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++岗面试真题解析 文章被收录于专栏
<p> C++工程师面试真题解析! </p> <p> 邀请头部大厂创作者<a href="https://www.nowcoder.com/profile/73627192" target="_blank">@Evila</a> 及牛客教研共同打磨 </p> <p> 助力程序员的求职! </p>