English | 中文版
[TOC]
#include <netinet/in.h>
struct sockaddr_storage {
uint8_t ss_len;
sa_family_t ss_family;
};#include <netinet/in.h>
struct in_addr {
in_addr_t s_addr; // 至少32位无符号整数类型
};
struct sockaddr_in {
uint8_t sin_len; // 长度字段
sa_family_t sin_family; // 地址族; 任何无符号整数类型
in_port_t sin_port; // TCP或UDP端口; 至少16位的无符号整数类型
struct in_addr sin_addr;
char sin_zero[8];
};#include <netinet/in.h>
struct in6_addr {
uint8_t s6_addr[16];
};
#defie SIN6_LEN
struct sockaddr_in6 {
uint8_t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
}- 从进程到内核传递套接字地址结构的函数
bindconnectsendto
- 从内核到进程传递套接字地址结构的函数
acceptrecvfromgetsocknamegetpeername
TODO
TODO
#include <sys/socket.h>
int socket(int family, int type, int protocol)-
family协议族family 说明 AF_INET IPv4协议 AF_INET6 IPv6协议 AF_LOCAL Unix域协议 AF_ROUTE 路由套接字 AF_KEY 密钥套接字 -
type套接字类型type 说明 SOCK_STREAM 字节流套接字 SOCK_DGRAM 数据报套接字 SOCK_SEQPACKET 有序分组套接字 SOCK_RAW 原始套接字 -
protocol协议类型protocol 说明 IPPROTO_TCP TCP传输协议 IPPROTO_UDP UDP传输协议 IPPROTO_SCTP SCTP传输协议 -
返回值成功:套接字描述符(非负)
失败:-1
创建套接字,返回套接字描述符
| AF_INET | AF_INET6 | AF_LOCAL | AF_ROUTE | AF_KEY | |
|---|---|---|---|---|---|
| SOCK_STREAM | TCP/SCTP |
TCP/SCTP |
是 | ||
| SOCK_DGRAM | UDP | UDP | 是 | ||
| SOCK_SEQPACKET | SCTP | SCTP | 是 | ||
| SOCK_RAW | IPv4 | IPv6 | 是 | 是 |
套接字family与type可能的组合
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)-
sockfd套接字描述符 -
servaddr指向套接字的地址 -
addrlen套接字地址长度 -
返回值成功:0
失败:-1
建立与TCP服务器的连接
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen)-
sockfd` 套接字
-
myaddr指向特定于协议的地址结构的指针 -
addrlen该地址结构的长度 -
返回值成功:0
失败:-1
绑定地址(把一个本地协议地址赋予一个套接字)
#include <sys/socket.h>
int listen(int sockfd, int backlog);-
sockfd套接字 -
backlog待处理的套接字队列的最大长度 -
返回值成功:0
失败:-1
监听套接字(把一个未连接的套接字转化为一个被动套接字,指示内核应接受指向该套接字的连接请求,同时设定排队的套接字队列的最大长度)
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);-
sockfd监听套接字 -
cliaddr返回已连接的协议地址 -
addrlen返回已连接的协议地址长度 -
返回值成功:一个全新的描述符
失败:错误码
接受连接(从已完成连接队列头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠)
#include <unistd.h>
int close(int sockfd);-
sockfd套接字文件描述符 -
返回值成功:0
出错:-1
将套接字标记为关闭,使它无法再被read或write调用
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);-
sockfd套接字文件描述符 -
localaddr本地协议地址 -
addrlen本地协议地址长度 -
返回值成功:0
失败:-1
返回与套接字关联的本地协议地址
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);-
sockfd套接字文件描述符 -
localaddr外地协议地址 -
addrlen外地协议地址长度 -
返回值成功:0
失败:-1
返回与套接字关联的外地协议地址(getpeername)
#include <sys/socket.h>
int shutdown(int sockfd, int howto);-
sockfd套接字文件描述符 -
howto动作动作 说明 SHUT_RD 关闭连接的读,丢弃接收缓冲区的数据。 SHUT_WR 关闭连接的写,丢弃发送缓冲区的数据。 SHUT_RDWR 关闭连接的读和写,类似于调用1次 SHUT_RD,又调用1次SHUT_WR。 -
返回值成功:0
失败:-1
关闭连接
#include <fcntl.h>
int fcntl(int fd, int cmd, ...)-
fd文件描述符 -
cmd命令 -
返回值成功:取决于cmd
出错:-1
执行各种描述符控制操作。
#include <unistd.h>
int ioctl(int fd, int request, ...);-
fd套接字 -
request请求 -
返回值成功:0
失败:-1
影响由fd引用的一个打开的文件。
| 操作 | fcntl | ioctl | 路由套接字 | POSIX |
|---|---|---|---|---|
设置套接字为非阻塞式I/O型 |
F_SETFL, O_NONBLOCK | FIONBIO | fcntl | |
设置套接字为信号驱动式I/O型 |
F_SETFL, O_ASYNC | FIOASYNC | fcntl | |
| 设置套接字属主 | F_SETOWN | SIOCSPGRP或FIOSETOWN | fcntl | |
| 获取套接字属主 | F_GETOWN | SIOCGPGRP或FIOGETOWN | fcntl | |
| 获取套接字接收缓冲区中的字节数 | FIONREAD | |||
| 测试套接字是否处于带外标志 | SIOCATMARK | sockatmark | ||
| 获取接口列表 | SIOCGIFCONF | sysctl | ||
| 接口操作 | SIOC[GS]IFxxx |
|||
| ARP告诉缓存操作 | SIOCxARP | RTM_xxx | ||
| 路由表操作 | SIOCxxxRT | RTM_xxx |
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);-
sockfd:套接字 -
level:级别,指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码 -
optname:opt名字 -
optval:指向一个变量 -
optlen:指定optval的大小 -
返回值
成功:0
出错:-1
获得套接字选项
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);sockfd:套接字level:级别,指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码optname:opt名字optval:指向一个opt变量optlen:指定optval的大小- 返回值
- 成功:0
- 出错:-1
设置套接字选项
TODO
| level(级别) | optname(选项名) | 数据类型 | 说明 |
|---|---|---|---|
| SOL_SOCKET | SO_BROADCAST SO_DEBUG SO_DONTROUTE SO_ERROR SO_KEEPALIVE SO_LINGER SO_OOBINLINE SO_RCVBUF SO_SNDBUF SO_RCVLOWAT SO_SNDLOWAT SO_RCVTIMEO SO_SNDTIMEO SO_REUSEADDR SO_REUSEPORT SO_TYPE SO_USELOOPBACK |
int int int int int linger int int int int int timeval timeval int int int int |
- 允许发送广播数据报 - 开启调试跟踪 - 绕过外出路由表查询 - 获取待处理错误并清除 - 周期性测试连接是否仍存活 - 若有数据待发送则延迟关闭 - 让接收到的带外数据继续在线留存 - 接收缓冲区大小 - 发送缓冲区大小 - 接收缓冲区低水位标记 - 发送缓冲区低水位标记 - 接收超时 - 发送超时 - 允许重用本地地址 - 允许重用本地端口 - 取得套接字类型 - 路由套接字取得所发送数据的副本 |
| IPPROTO_IP | IP_HDRINCL IP_OPTIONS IP_RECVDSTANDDR IP_RECVIF IP_TOS IP_TTL IP_MULTICAST_IF IP_MULTICAST_TTL IP_MULTICAST_LOOP IP_ADD_MEMBERSHIP IP_DROP_MEMBERSHIP IP_BLOCK_SOURCE IP_UNBLOCK_SOURCE IP_ADD_SOURCE_MEMBERSHIP IP_DROP_SOURCE_MEMBERSHIP |
int (见正文) int int int int in_addr{} u_char u_char ip_mreq{} ip_mreq{} ip_mreq_source{} ip_mreq_source{} ip_mreq_source{} ip_mreq_source{} |
- 随数据包含的IP首部 - IP首部选项 - 返回目的IP地址 - 返回接收接口索引 - 服务类型和优先权 - 存活时间 - 指定外出接口 - 指定外出TTL - 指定是否环回 - 加入多播组 - 离开多播组 - 阻塞多播组 - 开通多播组 - 加入源特定多播组 - 离开源特定多播组 |
| IPPROTO_ICMPV6 | ICMP6_FILTER | ivmp6_filter{} | - 指定待传递的ICMPv6消息类型 |
| IPPROTO_IPV6 | IPV6_CHECKSUM IPV6_DONTFRAG IPV6_NEXTHOP IPV6_PATHMTU IPV6_RECVDSTOPTS IPV6_RECVHOPLIMIT IPV6_RECVHOPOPTS IPV6_RECVPATHMTU IPV6_RECVPKTINFO IPV6_RECVRTHDR IPV6_RECVTCLASS IPV6_UNICAST_HOPS IPV6_USE_MIN_MTU IPV6_V60NLY IPV6_XXX IPV6_MULTICAST_IP IPV6_MULTICAST_HOPS IPV6_MULTICAST_LOOP IPV6_JOIN_GROUP IPV6_LEAVE_GROUP |
int int sockaddr_in6{} ip6_mtuinfo{} int int int int int int int int int int (见正文) u_int int u_int ipv6_mreq{} ipv6_mreq{} |
- 用于原始套接字的校验和字段偏移 - 丢弃大的分组而非将其分片 - 指定下一跳地址 - 获取当前路径MTU 接收目的地址选项 - 接收单播跳限 - 接收步跳选项 - 接收路径MTU - 接收分组信息 - 接收源路径 - 接收流通类型 - 默认单播跳限 - 使用最小MTU - 禁止v4兼容 - 粘附性辅助数据 - 指定外出接口 - 指定外出跳限 - 指定是否环回 - 加入多播组 - 离开多播组 |
| IPPROTO_IP IPPROTO_IPV6 |
MCAST_JOIN_GROUP MCAST_LEAVE_GROUP MCAST_BLOCK_SOURCE MCAST_UNBLOCK_SOURCE MCAST_JOIN_SOURCE_GROUP MCAST_LEAVE_SOURCE_GROUP |
group_req{} group_source_req{} group_source_req{} group_source_req{} group_source_req{} group_source_req{} |
- 加入多播组 - 离开多播组 - 阻塞多播源 - 开通多播源 - 加入源特定多播组 - 离开源特定多播组 |
| level(级别) | optname(选项名) | 数据类型 | 说明 |
|---|---|---|---|
| IPPROTO_TCP | TCP_MAXSEG TCP_NODELAY |
int int |
- TCP最大分节大小 - 禁止Nagle算法 |
| IPPROTO_SCTP | SCTP_ADAPTION_LAYER SCTP_ASSOCINFO SCTP_AUTOCLOSE SCTP_DEFAULT_SEND_PARAM SCTP_DISABLE_FRAGMENTS SCTP_EVENTS SCTP_GET_PEER_ADDR_INFO SCTP_I_WANT_MAPPED_V4_ADDR SCTP_INITMSG SCTP_MAXBURST SCTP_MAXSEG SCTP_NODELAY SCTP_PEER_ADDR_PARAMS SCTP_PRIMARY_ADDR SCTP_RTOINFO SCTP_SET_PEER_PRIMARY_ADDR SCTP_STATUS |
sctp_setadaption{} sctp_assocparams{} int sctp_sndrcvinfo{} int sctp_event_subscribe{} sctp_paddrinfo{} int sctp_initmsg{} int int int sctp_paddrparams{} sctp_setprim{} sctp_rtoinfo{} sctp_setpeerprim{} sctp_status{} |
- 适配层指示 - 检查并设置关联信息 - 自动关闭操作 - 默认发送参数 - SCTP分片 - 感兴趣事件的通知 - 获取对端地址状态 - 映射的v4地址 - 默认的INIT参数 - 最大猝发大小 - 最大分片大小 - 禁止Nagle算法 - 对端地址参数 - 主目的地址 - RTO信息 - 对端的主目的地址 - 获取关联状态 |
struct sockaddr_dl {
uint8_t sdl_len;
sa_family_t sdl_family;
uint16_t sdl_index;
uint8_t sdl_type;
uint8_t sdl_nlen;
uint8_t sdl_alen;
uint8_t sdl_slen;
char sdl_data[12];
};#include <sys/param.h>
#include <sys/sysctl.h>
int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
void *newp, size_t newlen);-
name名字(整数数组)-
AF_INET:获取或设置影响网际网协议的变量。
-
AF_LINK:获取或设置链路层信息(如:PPP接口的数目)。
-
AF_ROUTE:返回路由表或接口列表的信息
sysctl在AF_ROUTE域返回的信息:
name[] 返回IPv4路由表 返回IPv4 ARP高速缓存 返回IPv6路由表 返回接口清单 0 CTL_NET CTL_NET CTL_NET CTL_NET 1 AF_ROUTE AF_ROUTE AF_ROUTE AF_ROUTE 2 0 0 0 0 3 AF_INET AF_INET AF_INET6 0 4 NET_RT_DUMP NET_RT_FLAGS NET_RT_DUMP NET_RT_IFLIST 5 0 RTF_LLINFO 0 0 -
AF_UNSPEC:获取或设置一些套接字层变量(如:套接字发送或接收缓冲区的最大大小)。
-
-
namelen数组长度 -
oldp供内核存放名字的缓冲区可能包含以下信息:
- NET_RT_DUMP:返回由name[3]指定的地址族的路由表。如果所指定的地址族为0,那么返回所有地址族的路由表。
- NET_RT_FLAGS:返回由name[3]指定的地址族的路由表,但是仅限于带标示与由name[5]指定的标志相匹配的路由表项。路由表中所有ARP高速缓存表项均设置了RTF_LLINFO标志位。
- NET_RT_IFLIST:返回所有已配置接口的信息。
-
oldlenp供内核存放名字的缓冲区大小 -
newp缓冲区 -
newlen缓冲区大小 -
返回值成功:0
失败:-1
检查路由表和接口列表,创建路由套接字。
#include <sys/socket.h>
size_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen)-
sockfd描述符 -
buff缓冲区指针 -
nbytes读字节数 -
from发送者地址 -
addrlen地址长度 -
返回值成功:读字节数
失败:-1
收消息(recvfrom需要设置超时选项,否则会一直阻塞下去)。
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t *addrlen)-
sockfd描述符 -
buff缓冲区指针 -
nbytes读/写字节数 -
flags参数 -
to接收者地址 -
addrlen地址长度 -
返回值成功:写字节数
失败:-1
发消息
TODO
TODO
TODO
应用进程写入1字节带外数据后的套接字发送缓冲区
#include <sys/socket.h>
int sockatmark(int sockfd);-
sockfd套接字描述符 -
返回值1:处于带外标记
0:不处于带外标记
-1:失败
确定套接字是否处于带外标记
原始套接字提供普通的TCP和UDP套接字所不提供的以下能力:
- 进程可以读与写ICMPv4,IGMPv4和ICMPv6等分组。
- 进程可以读写内核不处理其协议字段的IPv4数据报。
- 进程还可以使用IP_HDRINCL套接字选项自行构造IPv4首部。
-
把第二个参数指定为SOCK_RAW并调用socket函数,以创建一个原始套接字;第三个参数(协议)通常不为0。
例:
int sockfd; sockfd = socket(AF_INET, SOCK_RAW, protocol); -
可以在原始套接字上开启IP_HDRINCL套接字选项。
例:
const int on = 1; if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) // 出错处理
-
可以在原始套接字上调用bind函数,用于设置从这个原始套接字发送的所有数据报的源IP地址(只在IP_HDRINCL套接字选项未开启的前提下)。
-
可以在这个原始套接字上调用connect函数,用于设置外地地址。
原始套接字的输出遵循以下规则:
-
普通输出通过调用sendto或sendmsg并指定目的IP地址完成。如果套接字已经连接,那么也可以调用write,writev或send。
-
如果IP_HDRINCL套接字选项未开启,那么由进程让内核发送的数据的起始地址指的是IP首部之后的第一个字节,因为内核将构造IP首部并把它置于来自进程的数据之前。内核把所构造IPv4首部的协议字段设置成来自socket调用的第三个参数。
-
如果IP_HDRINCL套接字选项已开启,那么由进程让内核发送的数据的起始地址指的是IP首部的第一个字节。进程调用输出函数写出的数据量必须包括IP首部的大小。整个IP首部由进程构造。
注意事项:
- IPv4标识字段可置为0,从而告知内核设置该值;
- IPv4首部校验和字段总是由内核计算并存储;
- IPv4选项字段是可选的。
-
内核会对超出外出接口MTU的原始分组执行分片。
IPv6原始套接字与IPv4相比存在如下差异:
- 通过IPv6原始套接字发送和接受的协议首部中的所有字段均采用网络字节序;
- IPv6不存在与IPv4的IP_HDRINCL套接字选项类似的东西。通过IPv6原始套接字无法读入或写出完整的IPv6分组(包括IPv6首部和任何扩展首部)。IPv6首部的几乎所有字段以及所有扩展首部都可以通过套接字选项或辅助数据由应用进程指定或获取。如果应用进程需要读入或写出完整的IPv6数据报,那就必须使用数据链路访问。
- IPv6原始套接字的校验和处理存在差异。
内核把接收到的IP数据报传递到原始套接字规则:
- 接收到的UDP分组和TCP分组和TCP分组绝不传递到任何原始套接字。
- 大多数ICMP分组在内核处理完其中的ICMP消息后传递到原始套接字。
- 所有IGMP分组在内核完成处理其中的IGMP消息后传递到原始套接字。
- 内核不认识其协议字段的所有IP数据报传递到原始套接字。
- 如果某个数据报以片段形式到达,那么在它的所有片段均到达且重组出该数据报之前,不传递任何片段分组到原始套接字。
内核对每个原始套接字均执行以下3个测试,只有这3个测试结果为真,内核才把接收到的数据报地送到这个套接字:
- 如果创建这个原始套接字时制定了非0的协议参数(socket的第三个参数),那么接收到的数据报的协议字段必须匹配该值,否则该数据报不递送到这个套接字。
- 如果这个原始套接字已由bind调用绑定了某个本地IP地址,那么接收到的数据报的目的IP地址必须匹配这个绑定地址,否则该数据报不递送到这个套接字。
- 如果这个原始套接字已由connect调用指定了某个外地IP地址,那么接收到的数据报的源IP地址必须匹配这个已连接地址,否则该数据报不递送到这个套接字。
#include <netinet/icmp6.h>
void ICMP6_FILTER_SETPASSALL(struct icmp6_filter *filt); // 指定所有消息类型都传递到应用进程
void ICMP6_FILTER_SETBLOCKALL(struct icmp6_filter *filt); // 指定不传递任何消息类型
void ICMP6_FILTER_SETPASS(int msgtype struct icmp6_filter *filt); // 放行某个指定消息类型到应用进程的传递
void ICMP6_FILTER_SETBLOCK(int msgtype, struct icmp6_filter *filt); // 阻止某个指定消息类型的传递
int ICMP6_FILTER_WILLPASS(int msgtype, const struct icmp6_filter *filt); // 判断消息类型是否被过滤器放行
int ICMP6_FILTER_WILLBLOCK(int msgtype, const struct icmp6_filter *filt);// 判断消息类型是否被过滤器阻止TCP/IP基于流的实现
struct strbuf {
int maxlen;
int len;
char *buf;
};#include <stropts.h>
int getmsg(int fd, struct strbuf *ctlptr, struct strbuf *dataptr, int *flagsp);-
fd文件描述符 -
ctlptr控制信息 -
dataptr数据 -
flagsp标志指针flagsp 说明 0 普通消息 RS_HIPRI 高优先级消息
接收控制信息和数据。
#include <stropts.h>
int putmsg(int fd, const struct strbuf *ctlptr, const struct strbuf *dataptr, int flags);-
fd文件描述符 -
ctlptr控制信息 -
dataptr数据 -
flags标志
发送控制信息和数据。
#include <stropts.h>
int getpmsg(int fd, struct strbuf *ctlptr, struct strbuf *dataptr, int *bandp, int *flagsp);fd文件描述符ctlptr控制信息dataptr数据bandp优先级带flagsp标志
接收带优先级的控制信息和数据。
#include <stropts.h>
int putpmsg(int fd, const struct strbuf *ctlptr, const struct strbuf *dataptr, int band, int flags);fd文件描述符ctlptr控制信息dataptr数据bandp优先级带flagsp标志
发送带优先级的控制信息和数据。
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[104];
};#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);-
family协议族(必须为AF_LOCAL) -
type类型(为SOCK_STREAM或SOCK_DGRAM中的一个) -
protocol协议(必须为0) -
sockfd用于返回新创建的套接字描述符 -
返回值成功:非0
失败:-1
创建两个随后连接起来的套接字
TODO
加载winsock DLL。
TODO
TODO
TODO
TODO
TODO
TODO
TODO
client.cpp:
#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <stdlib.h>
// 链接WS2_32
#pragma comment(lib, "ws2_32.lib")
#define BUFSIZE 4096 // 缓冲区大小
int main(int argc, char *argv[])
{
WSADATA wsd;
SOCKET sock;
char buf[BUFSIZE];
int ret;
struct sockaddr_in serv_addr;
unsigned short port;
struct hostent *host = NULL;
// 加载指定版本的dll
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
std::cout << "初始化失败!!!" << std::endl;
return 1;
}
// 创建socket
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
std::cout << "创建socket失败" << std::endl;
return 1;
}
// 指定服务器地址
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10086);
inet_pton(AF_INET, "localhost", (void*)&serv_addr.sin_addr.S_un.S_addr);
//serv_addr.sin_addr.s_addr = inet_addr("localhost"); // inet_addr已废弃
// 与服务器建立连接
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) {
std::cout << "connect()失败" << std::endl;
return 1;
}
// 收发消息
for (;;) {
gets_s(buf);
// 向服务器发送消息
ret = send(sock, buf, strlen(buf), 0);
if (ret == 0)
break;
else if (ret == SOCKET_ERROR) {
std::cout << "send()失败" << std::endl;
}
buf[ret] = '\0';
std::cout << "recv :" << ret << " bytes:" << buf;
}
// 关闭socket,释放资源
closesocket(sock);
WSACleanup();
system("pause");
return 0;
}serv.cpp:
#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")
#define DEFAULT_BUFFER 4096 // 缓冲区大小
// 与客户机通信的线程函数
DWORD WINAPI ClientThread(LPVOID lpParam)
{
SOCKET sock = (SOCKET)lpParam;
char buf[DEFAULT_BUFFER];
int ret, nleft, idx;
while (true) {
// 接收消息
ret = recv(sock, buf, DEFAULT_BUFFER, 0);
if (ret == 0)
break;
else if (ret == SOCKET_ERROR) {
std::cout << "recv() 失败:" << WSAGetLastError();
break;
}
buf[ret] = '\0';
std::cout << "recv: " << buf; // 打印消息
nleft = ret;
idx = 0;
while (nleft > 0) {
// 发送回应消息
ret = send(sock, &buf[idx], nleft, 0);
if (ret == 0)
break;
else if (ret == SOCKET_ERROR) {
std::cout << "send() 失败:" << WSAGetLastError();
break;
}
nleft -= ret;
idx += ret;
}
}
return 0;
}
int main()
{
WSADATA wsd;
HANDLE hThread;
DWORD dwThread;
SOCKET sListen, sClient;
int AddrSize;
unsigned short port;
struct sockaddr_in local, client;
// 加载winsock dll
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
std::cout << "winsock 初始化失败" << std::endl;
return 1;
}
// 创建socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sListen == SOCKET_ERROR) {
std::cout << "socket() 失败:" << WSAGetLastError();
return 1;
}
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
port = 10086;
local.sin_port = htons(port);
// 绑定socket
if (bind(sListen,
(struct sockaddr*)&local,
sizeof(local)) == SOCKET_ERROR) {
std::cout << "bind() 失败:" << WSAGetLastError();
return 1;
}
// 打开监听
listen(sListen, 0);
// 监听端口,创建线程
while (true) {
AddrSize = sizeof(client);
// 监听是否有连接请求
sClient = accept(sListen, (struct sockaddr*)&client, &AddrSize);
if (sClient == INVALID_SOCKET) {
std::cout << "accept() 失败:" << WSAGetLastError();
break;
}
// 创建线程
hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)sClient, 0, &dwThread);
if (hThread == NULL) {
std::cout << "CreateThread() 失败:" << GetLastError();
break;
}
// 处理完后关闭
CloseHandle(hThread);
}
closesocket(sListen);
WSACleanup();
system("pause");
return 0;
}- 当系统负载较轻时,没来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了。
- 相比传统的每个客户fork一次的设计范式,预先创建一个子进程池或一个线程池的设计范式能够把进程控制CPU时间降低10倍或以上。
- 某些实现允许多个子进程或线程阻塞在同一个accept调用中,另一些实现却要求包绕accept调用安置某种类型的锁加以保护。文件上锁或Pthread互斥锁上锁都可以使用。
- 让所有子进程或线程自行调用accept通常比让父进程或主线程独自调用accept并把描述符传递给子进程或线程来的简单而快速。
- 让所有子进程或线程阻塞在同一个accept调用中币让它们阻塞在同一个select调用中更可取。
- 使用线程通常远快于使用进程。
| 错误 | 说明 |
|---|---|
| EADDRINUSE | Address already in use,地址已使用 |
| ECONNREFUSED | 这是一种硬错误(hard error)。若对客户的SYN的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接。客户一接收到RST就马上返回ECONNREFUSED错误。RST是TCP在发生错误时发送的一种TCP分节。产生RST的三个条件: 1.目的地为某端口的SYN到达,然而该端口上没有正在监听的服务器 2.TCP想取消一个已有连接 3.TCP接收到一个根本不存在的连接上的分节 |
| ETIMEDOUT | TCP客户没有收到SYN分节的响应。 |
distination unreachable |
这是一种软错误(soft error)。客户主机内核保存该消息,并按第一种情况中所述的时间间隔继续发送SYN。若在某个规定的时间(4.4BSD规定75s)后仍未收到响应,则把保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。以下两种情形也是有可能的: 1.按照本地系统的转发表,根本没有到达远程系统的路径。 2.connect调用根本不等待就返回。 |
TODO
[1] (美)W.Richard Stevens, (美)Bill Fenner, (美)Andrew M Rudoff . Unix网络编程 卷一:套接字联网api . 3th Edition
[2] (美)Anthony Jones, Jim Ohlund . windows网络编程 . 2ED







