前言
复习网络编程选择题的随笔,记录了一些选择题涉及的知识点。
sockaddr、sockaddr_in、sockaddr_in6、sockaddr_storage
sockaddr
struct sockaddr
{
sa_family_t sa_family; //地址族AF_INET
char sa_data[14]; //ip地址及端口号
}
sockaddr_in
struct sockaddr_in {
short int sin_family; /* 地址类型 */
unsigned short int sin_port; /* 端口号,要用网络字节序表示 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
struct in_addr {
in_addr_t s_addr; // 32位IP地址
};
sockaddr_in6
struct sockaddr_in6
{
sa_family_t sin6_family; /* 地址协议簇: AF_INET6 */
u_int16_t sin6_port; /* 端口号,要用网络字节序表示 */
u_int32_t sin6_flowinfo /* 流信息,应设置为0 */
struct in6_addr sin6_addr; /* ipv6 地址结构体 */
u_int32_t sin6_socpe_id; /* scope ID, 尚处于实验阶段 */
};
struct in6_addr
{
unsigned char s6_addr[16]; /* 128位ipv6地址*/
};
inet_pton、inet_ntop
#include <arpe/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr); //将点分十进制的ip地址转化为用于网络传输的数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL
UDP套接字编程
- UDP中的服务器端和客户端没有连接:不必调用listen和accept函数,UDP中只有创建套接字和数据交换的过程。
- UDP服务器端和客户端均只需要1个套接字:在TCP中套接字是一对一关系,如果向10个客户端提供服务,需要10个服务器端套接字。但在UDP中,不管是服务器端还是客户端都只需要1个套接字。
- 客户端的connect可有可无,一般不需要,如果不用connect就是无连接UDP套接字,与同一目标主机进行长时间通信的效率会较低(因为sendto函数每次都要注册ip端口号、删除ip端口号);如果用了connect那就是已连接UDP套接字,可以提升对同一目标主机发送多个报文的效率。
- UDP未连接套接字指未注册目标地址信息的套接字,每次调用sendto函数都可以变更目标地址,向不同目标传输数据。
服务器的设计
1、并发服务器 vs 循环服务器
循环服务器便于构建和理解,但是性能差,并发服务器难于设计和构建,但是性能好。
2、面向连接 vs 无连接
面向连接服务器易于编程,TCP协议提供连接的可靠性,但是面临资源耗尽的危险。无连接服务器编程更复杂,因为需要承担可靠性的责任,但是广播或者组播只能用UDP。
3、多进程vs单进程
出现在服务器中的全部请求没有超过服务器处理的能力,则可以用单线程/进程。
各类型服务器的适用场合
1、用循环而不用并发:请求处理时间很短,循环方案产生的响应时间已经足够快了。
2、并发性选择:
- 单线程:创建线程/切换环境的开销很大,服务器必须在多个连接之间共享数据。
- 多线程:创建线程/切换环境的开销不大,服务器必须在多个连接之间共享数据。
- 多进程:创建线程/切换环境的开销不大,从进程可以孤立运行。
3、用无连接而不是面向连接:处理可靠性问题、每个客户访问它的服务器都是在同一个局域网中。
环回地址
IPV6:0:0:0:0:0:0:0:1,一般用零的压缩形式表示为 ::1。
IPv4:127.0.0.1。
connect调用
UDP的connect调用:仅仅在套接字的数据结构中记录下远程端点的的信息。不检测远程端点合法性,不意味着服务器可到达。
在UDP客户调用connect后(有连接UDP),可以使用send和recv来收发数据。如果没有调用connect(无连接UDP),只能用recvfrom和sendto收发数据。
bind调用
一个socket只能bind一次。
INADDR_ANY绑定熟知端口:路由器或者多接口机拥有多个IP地址,如果指明某个特定的IP地址,该套接字将不接受客户发到该机器其他IP地址上的通信内容。INADDR_ANY指明一个通配地址,与该主机上任何一个IP地址都相匹配,可以接受传入数据的目的地址是该主机上任意一个IP地址的通信。
recvfrom调用
若不需要查看收到的报文,则可以指定参数buf为NULL。(错的,但我不知道为啥)看了下例题,如果不需要查看,buf设置为1字节大小的数组。
shutdown调用
close()关闭套接字。
shutdown()调用实现部分连接关闭。
int shutdown(int sock, int direc);
在direc所指明的方向上关闭套接字sock。
direc的取值:
- 0:不允许输入。
- 1:不允许输出。
- 2:2个方向都关闭。
TCP的部分关闭:
UDP的部分关闭:在本地套接字中表明不期望在指定方向上传输数据了。
htons、htonl、ntohs、ntohl
h:host
n:network
l:long
s:short
网络字节顺序和主机字节顺序的转换。
在数据结构中存储的是网络字节顺序,通俗易懂的是主机字节顺序。
TCP、UDP使用场景
TCP:万维网(HTTP)、文件传输(FTP)、电子邮件(SMTP)。对传输效率要求低,但准确率要求高。
UDP:域名转换(DNS)、远程文件服务器(NFS)。对传输效率要求高,但准确率要求低。
for循环中的fork()
子进程也会继续fork()
inet_ntoa()、inet_aton()
inet_ntoa():从二进制转换到点分十进制表示的IP地址
char *inet_ntoa(struct in_addr in);
注意传入的参数是struct in_addr,与inet_ntop不一致。
inet_addr()
将包含 IPv4 点分十进制地址的字符串转换为in_addr结构的正确地址。
unsigned long inet_addr(const char* cp);
参数是一个字符串,返回值是一个unsigned long。
gethostbyname()与struct hostent
struct hostent
{
char *h_name; //正式主机名
char **h_aliases; //主机别名
int h_addrtype; //主机IP地址类型:IPV4-AF_INET
int h_length; //主机IP地址字节长度,对于IPv4是四字节,即32位
char **h_addr_list; //主机的IP地址列表
};
#define h_addr h_addr_list[0] //保存的是IP地址
struct hostent *gethostbyname(const char *hostname);
无状态服务器
无状态服务器处理的客户信息必须全部来自于请求所携带的信息以及其他服务器自身所保存的、并且可以被所有请求所使用的公共信息。
要求每个客户请求都指定文件名、文件中的位置、读取的字节数。
htons()在intelx86机器
我们常用的 x86 CPU (intel, AMD) 电脑是小端模式 little-endian,也就是整数的低位字节放在内存的低字节处。
网络字节序是大端模式big-endian,也就是整数的高位字节存放在内存的低地址处。
htons()是针对16进制数的表示。
假定你的port是0x1234,在网络字节序里应该显示addr addr+1 0x12 0x34 而在x86电脑上,0x1234放到内存中实际是:addr addr+1 0x34 0x12。
简单总结一下,就是x86机器和网络字节流的存储顺序相反,把数变成16进制,然后每2位倒一个顺序,再变回10进制。