基本TCP套接口编程
并发服务器:这是unix的一项技术,当有大量客户端到同一服务器的连接的时候,服务器给每一个客户端的连接都fork一个子进程,已达到并发的目的。
int socket(int sa_family, int type, intprotol)
sa_family 是协议族type是套接字类型,protol是协议(这个一般都是0)
协议族 | 解释 |
AF_INET | Ipv4协议族 |
AF_INET6 | Ipv6协议族 |
AF_LOCAL | UNIX 域协议族 |
AF_ROUTE | 路由套接口 |
AF_KEY | 密钥套接口
|
类型 | 解释 |
SOCK_STREAM | 字节流套接口 |
SOCK_DGRAM | 数据报套接口 |
SOCK_RAW | 原始套接口 |
socket在成功返回的时候返回一个很小的非负整数,和文件描述符一样,简称套接口
2 客户通过connect和服务器建立连接
int connect(int sock_fd, const strcutsockaddr* addr, socklen_t *len);
客户在调用connect之前不用进行bind操作,内核会选用一个ip和临时端口号来绑定到这个套接字(也可以进行bind)
关于connect的返回值:
ETIMEOUT:当connect调用的时候会进行三次握手,之前的文章已经说过了,如果客户端没有收到服务器对SYN的ACK数据报,就会返回ETIMEOUT(客户端在返回这个错误之前会重发SYN数据报)
ECONNREFUSED:当connect调用的时候,发送SYN同步数据报建立连接,服务器收到该数据报的时候,但是在connect上的地址和端口上没有监听服务器进程,服务器就返回ECONNREFUSED错误
EHOSTUNREACH:当客户端发送的SYN数据报在中间路由器引发不可达的信息,该中间路由器会返回给客户端一个ICMP数据报,客户端就会收到EHOSTUNREACH这个错误码
如果connect调用失败,不能再用该套接口,必须进行close操作(原来如此)
bind函数:
int bind(int sock_fd, const strcut sockaddr * addr, socklen_t *len);
一般TCP 服务器都会调用bind 以此来确定监听套接字的 ip地址和port,如果服务器不指定的话,就会使用某个ip(多ip的情况下)和一个临时端口号,但是这样客户端就不知道服务器的具体地址和端口号,这样的话,当客户端进行connect的时候,就不知道具体服务器的地址了,就建立不了连接了。
如果端口号是0 的话,就使用临时端口号
Ipv4的通配地址是INADDR_ANY这个会让内核决定使用哪一个ip(多ip的路由器或主机)
如果端口号使用的是0 ,内核为该套接字使用临时端口号,之后如果想或者这个临时端口号的话,就调用getsockname来返回协议地址
listen函数
int listen(int sock_fd, int waitqueue)
内核要维护两个队列:
未完成连接队列:当服务器收到客户端的SYN同步数据报的时候,就会在内核的为完成队列中建立一个条目,这样的套接字处于SYN_REVD状态
已完成连接队列:当服务器对SYN数据报发出确认,同时客户端收到服务器发送的对syn数据报进行确认的数据报之后,客户端会发送给服务器ack的ack数据报,这个时候,就会在已完成队列中建立一个条目,同时取消未完成队列的相应的条目信息
当调用accept的时候,是在已完成队列中的拿走一个条目,如果这个已完成队列是空的那么accept就会进入睡眠状态
当这两个队列是满的话,就会直接丢掉这个SYN数据报或者ack数据报,等待客户端进行重发。这时候服务器处理以完成队列和未完成队列,这个时候,就能是队列不那么拥挤,就有可能对重发的syn数据报进行处理。
accept函数:
int accept(int listen_fd , strcut sockaddr* addr, socklen_t *len);
listen_fd 是监听套接字
addr是客户端地址
len是你告诉内核想要获取的长度
如果对客户端的ip和port不感兴趣的话,可以把这两个值设置为NULL
并发服务器:
并发最简单的就是为每一个客户端都fork子进程,让子进程来完成每一个客户端的请求
close操作
int close(int fd)
这个操作并不是每次都发送FIN数据报(finish),一般发送的时候是这个套接字的引用计数已经是0了,一般当调用fork之前,客户端和服务器进行完了三次握手,完成了内核建立新的发送socket的时候,这之后,调用fork函数,子进程对对父进程的资源进行拷贝,当然会有 socket套接字(相当于问价描述符)这个时候,就相当于打开了两次,访问计数为2 ,当父进程建立完子进程的时候,就直接对发送套接字进行close是这个发送套接字的访问计数减一,当子进程完成数据的发送接收的时候,就进行close操作,这个时候,才进行发送FIN字节的操作
如果我们确实想对客户端发送FIN(不考虑访问计数的问题)就对套接字调用shutdown操作
int shutdown(int s, int how);
The shutdown() call causes all or part of a full-duplex connection on thesocket associated with s to be shut down. If how is SHUT_RD, further receptions will be disallowed. If how is SHUT_WR, further trans-missions will be disallowed. If how is SHUT_RDWR, further receptions andtransmissions will be disallowed.
获得套接口相关联的地址信息:
int getsockname(int sock_fd, strcutsockaddr * addr, socklen_t * len )
这个函数是或者套接口的本地地址信息
int getpeername(int sock_fd, structsockaddr* addr, socklen_t* len)
这个是获得与sock_fd连接上的客户端的地址
这里的这个sock_fd 参数是调用的已经建立连接的socket,connect 或accept之后的套接字
在睡梦中看书,有点不爽。不过stevens的看的还是蛮爽的