C1(Linux)
-
服务器编程
-
步骤
-
创建套接字 socket()
-
函数原型
int socket (int __domain, int __type, int __protocol) __THROW;
-
参数
__domain: 协议族 PF_INET / PF_INET6
__type: 协议族下数据传输类型 SOCK_STREAM / SOCK_DGRAM
__protocal: TCP/UDP 协议只需填0。如果 domain 和 type 还无法确认具体的协议,则此项选择具体的协议。
-
返回值
成功: 文件描述符; 失败: -1
-
常用
int serv_tcp_sock = socket(PF_INET, SOCK_STREAM, 0); // TCP , ipv4 int serv_udp_sock = socket(PF_INET, SOCK_DGRAM, 0); // udp , ipv4
-
-
设置服务器 ip+port ==> bind()
-
函数原型
bind (int __fd, const struct sockaddr * __addr, socklen_t __len)
-
参数
__fd: 要绑定的sockfd。一般为 socket() 返回值。
__addr: 服务器ip+port。sockaddr 类型。
socklen_t: sizeof(__addr)。
-
__addr 结构说明
-
sockaddr 结构
struct sockaddr { sa_family_t sin_family; // 地址族 char sa_data[14]; // 地址信息 };
-
sockaddr_in 结构
struct sockaddr_in { sa_family_t sin_family; // 地址族 AF_INET / AF_INET6 uint16_t sin_port; // 16位TCP/UDP端口号 struct in_addr sin_addr; //32位IPv4地址 char sin_zero[8]; // 不使用 }; struct in_addr { uint32_t s_addr; }; // 以上内容都以网络字节序保存 // 主机字节序 <==> 网络字节序 见下文
-
sockaddr 与 sockaddr_in 的关系
-
sockaddr 用于保存地址信息,可以是ipv4 or ipv6;
-
sockaddr_in 专用于表示 ipv4 地址。
-
bind() 中 _addr 要求 sockaddr 类型, 因此需要定义 sockaddr_in 的ipv4地址后要强制转换为sockaddr类型。
-
-
-
主机字节序与网络字节序转换
-
主机字节序:每台机器都有自己的字节序表示和存储数据。分为大端序和小端序。大端序:高位字节存放到低位地址;小端序:高位字节存放到高位地址。常见的主机字节序都是小端序。
-
一段代码测试主机字节序是大端序还是小端序
// TODO: 测试大小端
-
-
网络字节序:在网络中传输数据需要保证双方使用的字节序一样。因此规定数据在网络中传输时统一使用大端序,网络字节序即大端序。
-
数据在传输前都要经过转换吗?
只需要向sockaddr_in 结构体中传入大端序的ip+port, 其他情况无需考虑字节序问题,这个过程是自动的。
-
转换函数
-
整型转换(通常用于整型的port与ip,如INADDR_ANY)
-
函数原型
uint32_t htonl (uint32_t __hostlong);
uint16_t htons (uint16_t __hostshort);
uint32_t ntohl (uint32_t __netlong);
uint16_t ntohs (uint16_t __netshort);
-
说明
h: host, 表示主机序
n: network, 表示网络序
s: short, 表示16bit整型(通常用于port)
l: long, 表示32bit整型(通常用于ip)
-
-
字符串转整型(通常用于字符串型的ip地址转换)
-
函数原型1
in_addr_t inet_addr (const char * string);
-
说明1
成功: 返回32位大端序整数值
失败:返回 INADDR_NONE
参数:字符串ip地址
-
使用1
sockaddr_in serv_addr; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
-
函数原型2
int inet_aton (const char * string, struct in_addr * addr);
-
说明2
成功: 1; 失败: 0
string: ip地址
成功后将转换后的IP地址直接存放在 sockaddr_in 结构体中的 in_addr 结构体变量。
-
使用2
sockaddr_in serv_addr; if (!inet_aton(addr, &addr_inet.sin_addr)) {//error }
-
-
辅助函数 atoi
-
函数原型
int atoi (const char *__nptr)
-
说明
atoi 是标准库函数,用于将字符型数字转换为数字
-
使用场景
客户端网络编程中接收用户输入 port ,将其转换为整型后转网络字节序。
-
使用
sockaddr_in serv_addr; serv_addr.sin_port = htons(atoi("6666"));
-
-
-
-
-
返回值
成功: 0; 失败: -1
-
常用
sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); // 清0主要清的是最后8字节的sin_zero参数 serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // htonl : 转ip serv_addr.sin_port = htons(SERV_PORT); // htons: 转port bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr));
-
-
开启监听状态 & 设置最大监听队列 listen()
-
函数原型
int listen (int __fd, int __n);
-
参数
__fd: 服务端 socket fd
__n: 最大监听队列
-
返回值
成功: 0; 失败: -1
-
-
等待接受客户端连接 accept() (被动)
-
函数原型
int accept (int fd, struct sockaddr * addr, socklen_t * addr_len);
-
参数
fd: 处于监听状态的 socket fd
addr: fd 绑定的地址, sockaddr 类型
addr_len: 地址大小,指针类型。
-
返回值
成功: 连接到的客户端文件描述符; 失败: -1
-
常用
int clnt_sock; sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (sockaddr*)&clnt_addr, &clnt_addr_size);
-
-
读写数据 read() / write()
-
函数原型
ssize_t write (int __fd, const void *__buf, size_t __n);
ssize_t read (int __fd, void *__buf, size_t __nbytes)
-
参数
fd: 打开的文件描述符。
buf: read – 读取后存放的缓冲区; write – 要写的内容。
n/nbytes: read – 读取的最大字节数; write – 要写的字节数。
-
返回值
成功: 接收/写入的字节数。 失败: -1
-
-
关闭连接,释放内存 close()
-
-
完整代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <iostream> #define SERV_PORT 6666 #define MAX_LISTEN_QUEUE 5 int main(int argc, char* argv[]) { // 1. socket 创建套接字 int serv_sock; serv_sock = socket(PF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { std::cout << "socket() error!" << std::endl; } // 2. 设置服务器 ip + port ==> bind() 绑定 sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_PORT); if (bind(serv_sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { std::cout << "bind() error!" << std::endl; } // 3. 开启监听状态 listen() , 设置最大监听队列 if (listen(serv_sock, MAX_LISTEN_QUEUE) == -1) { std::cout << "listen() error!" << std::endl; } std::cout << "server start successfully! IP: " << ntohl(serv_addr.sin_addr.s_addr) << " Port: " << SERV_PORT << std::endl; // 4. 等待接受客户端连接 accept() int clnt_sock; sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (sockaddr*)&clnt_addr, &clnt_addr_size); if (clnt_sock == -1) { std::cout << "accept() error!" << std::endl; } // 5. 读写数据 -- 向客户机写入 hello clnt_addr char msg[] = "hello socket!"; write(clnt_sock, msg, sizeof(msg)); // 6. 释放内存 close(clnt_sock); close(serv_sock); return 0; }
-
-
客户端编程
-
步骤
-
创建套接字 socket()
-
连接服务器 connect() (主动)
-
函数原型
int connect (int __fd, const struct sockaddr * __addr, socklen_t __len);
-
参数
fd: 客户端socket
__addr: 服务器地址
__len: 服务器地址长度
-
返回值
成功0; 失败-1
-
常用
// 填充服务器地址信息 sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(atoi("6666")); // 连接服务器 connect(sock, (sockaddr*)&serv_addr, sizeof(serv_addr));
-
-
读写数据
-
释放内存
-
-
完整代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <iostream> int main(int argc, char* argv[]) { if (argc != 3) { std::cout << "Usage: " << argv[0] << "<IP> <port>" << std::endl; } // 1. 创建套接字 socket() int sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) { std::cout << "socket() error!" << std::endl; } // 2. 设置要连接服务器的 ip + port, 连接 ==> connect sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); if (connect(sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) { std::cout << "connect() error!" << std::endl; return 1; } std::cout << "Connect Successfully!" << std::endl; // 3. 读取数据 char msg[30]; int read_len = read(sock, msg, sizeof(msg)-1); if (read_len == -1) { std::cout << "read() error!" << std::endl; } std::cout << "Message from server: " << msg << std::endl; // 4. 释放内存 close(sock); return 0; }
-
-
Cookies
-
面向连接的套接字(SOCK_STREAM)与面向消息的套接字(SOCK_DGRAM)
-
路由器和交换机
-
网络接口卡(NIC)
-
127.0.0.1回环地址
-
文件
-
句柄(windows)与文件描述符(linux)
-
windows中区分 socket 操作和文件操作; Linux中不区分
-
linux 标准输入输出及错误的文件描述符
文件描述符 对象 0 标准输入 1 标准输出 2 标准错误
-
-
文件操作
-
头文件
<unistd.h>
-
打开文件
-
函数原型
int open(const char* path, int flag);
-
文件打开模式
打开模式 含义 O_CREAT 必要时创建文件 O_TRUNC 删除全部现有数据 O_APPEND 维持现有数据,追加新数据到末尾 O_RDONLY 只读打开 O_WRONLY 只写打开 O_RDWR 读写打开 -
返回值
成功: 文件描述符; 失败: -1
-
-
关闭文件
-
函数原型
int close(int fd);
-
返回值
成功: 0; 失败: -1
-
-
-
-
以_t为后缀的数据类型
size_t: unsigned int
ssize_t: signed int
这些都是元数据类型
-
C1(Windows)
- 服务器编程
- 客户端编程