socket编程作业笔记

Ubuntu16.04 下 C/C++,通过socket编程实现server与client间通信时遇到的问题。

类型转换

  • 在主机字节序与网络字节序中转换

x86CPU都是小端字节序(little-endian),地址低位存储低位值。而网络字节序与CPU类型和操作系统无关,是大端字节序(big-endian),低位地址存储高位值,从地址小向大(左向右)看符合人的直观感受。

htonl()htons()等函数将主机字节序转换为网络字节序。

1
2
3
#include <arpa/inet.h>
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);//自定义端口号
  • 字符串类型的IP地址和二进制类型

通常需要将命令行中输入的点分十进制ip地址转换为二进制类型的地址,或者将二进制类型的地址转换为点分十进制ip地址并输出显示。

函数原型

1
2
int inet_pton(int af, const char* src, void* dst);
int inet_ntop(int af, const void* src, char* dst, socklen_t cnt);

将命令行输入的地址存入字符串,使用inet_pton()处理。

1
2
3
#include <sys/socket.h>
#include <arpa/inet.h>
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

bzero()和memset()

有时需要执行清0操作。

  • bzero()
1
2
#include <string.h>
void bzero(void *s, int n)

将s所指内存的前n个字节清0

bzero()只有2个参数,它不是ANSI C函数,其起源于早期的Berkeley网络编程代码,但是几乎所有支持套接字API的厂商都提供该函数。

  • memset()
1
2
#include <string.h>
void *memset(void *buffer, int c, int count)

将buffer所指内存的前count个字节设为c

memset()除清0外还能设其他值,但在第2和3个参数互换位置时,编译器不能发现。它是ANSI C函数,更常规、用途更广。

如果只想清0,则用bzero()更方便。在设置Internet socket地址前,先将其所有字节清零。因为struct sockaddr_in中有8个填充字节,清0避免异常。

1
2
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));

使用INADDR_ANY作为ip地址

在为udp服务器程序设置ip地址时,使用INADDR_ANY做ip。

1
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

winsock中定义

1
#define INADDR_ANY              (ULONG)0x00000000

INADDR_ANY即表示0.0.0.0,也就是“任意地址”、“所有地址”,在这里表示本机的所有IP。

有些主机的网卡数量不止一块,如果某套接字只绑定某一特定的ip,那么它就只能监听此ip地址所对应的网卡端口。如果想同时监听多个物理端口,则需要写3个套接字,较为繁琐。

而绑定0.0.0.0时,只需要管理1个套接字,此时无论数据从哪一网卡端口进入,都可以收到。因此使用INADDR_ANY作为ip,减少管理开销。

初步使用线程

无论是server还是client,收信和发信都要同步进行,因而需要使用至少2个线程。

以client为例,创建一个收信线程和一个发信线程。截取部分代码如下:

1
2
3
4
5
6
#include <thread>
#include <functional>
std::thread t1(std::bind(&ClientSession::readThread, this));
t1.detach();
std::thread t2(std::bind(&ClientSession::writeThread, this));
t2.join();

t2.join();的目的是让当前主线程等待所有的子线程执行完,才执行主线程能后续语句

t1.detach();的目的是让子线程脱离主线程,二者独立运行。

以上代码在创建线程时调用了类内的函数,因而比较繁琐。一般而言,创建线程只需

1
std::thread t(func);//func是该线程执行的函数名

学画流程图

撰写实验报告时需要画流程图,用markdown画

  • client主进程
1
2
3
4
5
6
7
8
9
10
st=>start: 开始
init=>operation: 初始化
login=>operation: 登录、选择联系人、开启读写会话子线程
wait=>condition: 读写子线程是否结束
ed=>end: 结束
ed2=>end
ed3=>end
st->init->login->wait
wait(yes)->ed
wait(no)->wait
1
2
3
4
5
6
7
8
9
10
st=>start: 开始
init=>operation: 初始化
login=>operation: 登录、选择联系人、开启读写会话子线程
wait=>condition: 读写子线程是否结束
ed=>end: 结束
ed2=>end
ed3=>end
st->init->login->wait
wait(yes)->ed
wait(no)->wait
  • 收数据子线程
1
2
3
4
5
6
st=>start: 收数据线程开始
t=>condition: 是否收到字符串"bye"
ed=>end: 收数据线程结束
st->t
t(yes)->ed
t(no)->t
1
2
3
4
5
6
st=>start: 收数据线程开始
t=>condition: 是否收到字符串"bye"
ed=>end: 收数据线程结束
st->t
t(yes)->ed
t(no)->t
  • 读数据子线程
1
2
3
4
5
6
st=>start: 读数据线程开始
t=>condition: 是否发送字符串"exit"
ed=>end: 读数据线程结束
st->t
t(yes)->ed
t(no)->t
1
2
3
4
5
6
st=>start: 读数据线程开始
t=>condition: 是否发送字符串"exit"
ed=>end: 读数据线程结束
st->t
t(yes)->ed
t(no)->t
  • server进程
1
2
3
4
5
6
7
8
9
st=>start: 开始
init=>operation: 初始化
login=>operation: (若存在,则)处理用户的登陆、指定
联系人、开启关闭会话和信息传送等请求
exit=>condition: 是否关闭服务器进程
ed=>end: 结束
st->init->login->exit
exit(no)->login
exit(yes)->ed
1
2
3
4
5
6
7
8
9
st=>start: 开始
init=>operation: 初始化
login=>operation: (若存在,则)处理用户的登陆、指定
联系人、开启关闭会话和信息传送等请求
exit=>condition: 是否关闭服务器进程
ed=>end: 结束
st->init->login->exit
exit(no)->login
exit(yes)->ed

学画时序图

撰写实验报告时需要画时序图,用markdown画

  • 实验所用收发数据时序图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Title: 收发数据时序图
Note left of PC1: PC1客户端进程
PC1->server: hi! here is PC1.
server->PC2: hi! here is PC1.
Note right of PC2: PC2客户端进程
PC1->server: I'm PC1.
server->PC2: I'm PC1.
PC2-->server: hello, I'm PC2.
server-->PC1: hello, I'm PC2.
PC1->server: exit
note over PC1,server: 会话结束
server->PC2: exit
PC2-->server: exit
note over server,PC2: 会话结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Title: 收发数据时序图
Note left of PC1: PC1客户端进程
PC1->server: hi! here is PC1.
server->PC2: hi! here is PC1.
Note right of PC2: PC2客户端进程
PC1->server: I'm PC1.
server->PC2: I'm PC1.
PC2-->server: hello, I'm PC2.
server-->PC1: hello, I'm PC2.
PC1->server: exit
note over PC1,server: 会话结束
server->PC2: exit
PC2-->server: exit
note over server,PC2: 会话结束