目录
socket 编程接口
socket 常见 API
C
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockaddr 结构
socket API 是一层抽象的网络编程接口 , 适用于各种底层网络协议 , 如 IPv4 、 IPv6, 以及
后面要讲的 UNIX Domain Socket. 然而 , 各种网络协议的地址格式并不相同 .
•
IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中 ,IPv4 地址用 sockaddr_in 结构
体表示 , 包括 16 位地址类型 , 16 位端口号和 32 位 IP 地址 .
•
IPv4 、 IPv6 地址类型分别定义为常数 AF_INET 、 AF_INET6. 这样 , 只要取得某
种 sockaddr 结构体的首地址 , 不需要知道具体是哪种类型的 sockaddr 结构体 , 就可
以根据地址类型字段确定结构体中的内容 .
•
socket API 可以都用 struct sockaddr * 类型表示 , 在使用的时候需要强制转化成
sockaddr_in; 这样的好处是程序的通用性 , 可以接收 IPv4, IPv6, 以及 UNIX Domain
Socket 各种类型的 sockaddr 结构体指针做为参数 ;
sockaddr 结构

sockaddr_in 结构
虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时 , 使用的数据结
构是 sockaddr_in; 这个结构里主要有三部分信息 : 地址类型 , 端口号 , IP 地址
in_addr 结构

in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数;
UDP 网络编程
V1 版本 - echo server
简单的回显服务器和客户端代码
备注 : 代码中会用到 地址转换函数 . 参考接下来的章节 .
nocopy.hpp
C++
#pragma once
#include <iostream>
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy &) = delete;
const nocopy& operator = (const nocopy &) = delete;
~nocopy(){}
};
UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"
const static uint16_t defaultport = 8888;
const static int defaultfd = -1;
const static int defaultsize = 1024;
class UdpServer : public nocopy
{
public:
UdpServer(uint16_t port = defaultport)
: _port(port), _sockfd(defaultfd)
{
}
void Init()
{
// 1. 创建 socket,就是创建了文件细节
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
lg.LogMessage(Fatal, "socket errr, %d : %s\n", errno,
strerror(errno));
exit(Socket_Err);
}
lg.LogMessage(Info, "socket success, sockfd: %d\n",
_sockfd);
// 2. 绑定,指定网络信息
struct sockaddr_in local;
bzero(&local, sizeof(local)); // memset
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY; // 0
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 4
字节 IP 2. 变成网络序列
// 结构体填完,设置到内核中了吗??没有
int n = ::bind(_sockfd, (struct sockaddr *)&local,
sizeof(local));
if (n != 0)
{
lg.LogMessage(Fatal, "bind errr, %d : %s\n", errno,
strerror(errno));
exit(Bind_Err);
}
}
void Start()
{
// 服务器永远不退出
char buffer[defaultsize];
for (;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 不能乱写
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) -
1, 0, (struct sockaddr *)&peer, &len);
if (n > 0)
{
InetAddr addr(peer);
buffer[n] = 0;
std::cout << "[" << addr.PrintDebug() << "]# " <<
buffer << std::endl;
sendto(_sockfd, buffer, strlen(buffer), 0, (struct
sockaddr *)&peer, len);
}
}
}
~UdpServer()
{
}
private:
// std::string _ip; // 后面要调整
uint16_t _port;
int _sockfd;
};
InetAddr.hpp
C++
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class InetAddr
{
public:
InetAddr(struct sockaddr_in &addr):_addr(addr)
{
_port = ntohs(_addr.sin_port);
_ip = inet_ntoa(_addr.sin_addr);
}
std::string Ip() {return _ip;}
uint16_t Port() {return _port;};
std::string PrintDebug()
{
std::string info = _ip;
info += ":";
info += std::to_string(_port); // "127.0.0.1:4444"
return info;
}
~InetAddr(){}
private:
std::string _ip;
uint16_t _port;
struct sockaddr_in _addr;
};
Comm.hpp
C++
#pragma once
enum{
Usage_Err = 1,
Socket_Err,
Bind_Err
};
•
Log.hpp 已经有了,这里就不再复制粘贴了
•
云服务器不允许直接 bind 公有 IP ,我们也不推荐编写服务器的时候, bind 明确
的 IP ,推荐直接写成 INADDR_ANY
在网络编程中,当一个进程需要绑定一个网络端口以进行通信时,可以使用
INADDR_ANY 作为 IP 地址参数。这样做意味着该端口可以接受来自任何 IP 地址的连
接请求,无论是本地主机还是远程主机。例如,如果服务器有多个网卡(每个网卡上
有不同的 IP 地址),使用 INADDR_ANY 可以省去确定数据是从服务器上具体哪个网
卡 /IP 地址上面获取的。
UdpClient.hpp
C++
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void Usage(const std::string &process)
{
std::cout << "Usage: " << process << " server_ip server_port"
<< std::endl;
}
// ./udp_client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
// 1. 创建 socket
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error: " << strerror(errno) <<
std::endl;
return 2;
}
std::cout << "create socket success: " << sock << std::endl;
// 2. client 要不要进行 bind? 一定要 bind 的!!
// 但是,不需要显示 bind,client 会在首次发送数据的时候会自动进行
bind
// 为什么?server 端的端口号,一定是众所周知,不可改变的,client 需
要 port,bind 随机端口.
// 为什么?client 会非常多.
// client 需要 bind,但是不需要显示 bind,让本地 OS 自动随机 bind,
选择随机端口号
// 2.1 填充一下 server 信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
while (true)
{
// 我们要发的数据
std::string inbuffer;
std::cout << "Please Enter# ";
std::getline(std::cin, inbuffer);
// 我们要发给谁呀?server
ssize_t n = sendto(sock, inbuffer.c_str(),
inbuffer.size(), 0, (struct sockaddr*)&server, sizeof(server));
if(n > 0)
{
char buffer[1024];
//收消息
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t m = recvfrom(sock, buffer, sizeof(buffer)-1,
0, (struct sockaddr*)&temp, &len); // 一般建议都是要填的.
if(m > 0)
{
buffer[m] = 0;
std::cout << "server echo# " << buffer <<
std::endl;
}
else
break;
}
else
break;
}
close(sock);
return 0;
}
•
client 端要不要显示 bind 的问题