linux
套接字编程
socket
#include <sys/socket.h>
int socket(int family, int type, int protocol);
若成功返回非负描述符,出错返回-1
family: 协议族 AF_INET
type: 套接字类型 SOCK_STREAM
protocol:协议类型常值 0
connect 客户用connect来建立与TCP服务器的连接
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
成功返回0,出错返回-1
sockfd: socket返回的套接字描述符
*servaddr: 指向一个套接字地址结构的指针 (SA *)&cliaddr
addrlen: 套接字大小 sizeof(cliaddr)
bind 把一个本地协议地址绑定一个套接字 //服务端
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
成功返回0,出错返回-1
sockfd:套接字描述符
myaddr:本地套接字 servaddr
addrlen:siezof(servaddr)
listen 仅由服务器调用,1将未连接套接字转换成被动套接字,2规定了该套接字的最大连接个数
#include <sys/socket.h>
int listen(int sockfd, int backlog);
通常在调用socket和bind函数之后,并在调用accept之前调用
backlog = 未完成连接队列 + 已完成连接队列之和
accept 从已完成连接队列队头返回下一个已完成连接
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
cliaddr: 客户的套接字 (SA *) &cliaddr
addrlen:客户套接字地址引用 &cli_len
如果我们对客户的协议地址不感兴趣,可以置为空指针
close 关闭套接字,终止TCP连接
#include <unistd.h>
int close(int sockfd);
Sample:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include "../request.h"
int main(int argc, char *argv[])
{
int listenfd, connfd;
struct sockaddr_in servaddr, cliaddr; //两个套接字结构,一个服务器,一个客户
socklen_t serv_len, cli_len;
listenfd = socket(AF_INET, SOCK_STREAM, 0); //套接字listenfd
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //自动源主机ip
servaddr.sin_port = htons(9999);
//serv_len = sizeof(servaddr);
bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); //绑定
listen(listenfd, 100); //转换成被动套接字,最大连接数量100
while(1)
{
char ch[5000];
std::string rec;
cli_len = sizeof(cliaddr);
connfd = accept(listenfd, (SA *) &cliaddr, &cli_len); //accept
int n;
while((n = read(connfd, &ch, 5000)) > 0)
{
ch[n] = 0;
rec += ch;
}
write(connfd, &send_str, sizeof(send_str)/sizeof(send_str[0]));
close(connfd);
}
}
getsockname & getpeername
这两个函数或者返回与某个套接字关联的本地协议地址,或者返回与某个套接字关联的外地协议地址
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
- 在一个没有调用bind的TCP客户上,connect返回成功后,getsockname用于返回由内核赋予的本地ip地址和本地端口号
- 在以端口号0调用bind(告诉内核去选择本地端口号)后,getsockname用于返回由内核赋予的本地端口号
并发
- fork
#include <unistd.h>
pid_t fork(void);
返回:在子进程种为0, 在父进程种为子进程id,出错为-1
子进程可以通过调用getppid得到父进程的ID,父进程记录每次调用fork的返回值来记录子进程ID
pid_t pid;
int listenfd, connfd;
listenfd = socket(.....);
bind(listenfd,...);
listen(listenfd, 100);
while(1) {
connfd = accept(listenfd, ....);
if( (pid = fork()) == 0) { //fork创建子进程
close(listenfd); //子进程关闭监听套接字
doit(connfd); //process the request
close(connfd); //处理完关闭已连接套接字
exit(0);
}
/*
line 10-15 是子进程
*/
close(connfd); //父进程
}
每个文件或描述符都有一个引用计数,引用计数在文件表项中维护,它是当前打开着的引用该文件或套接字的描述符的个数
上面代码中 socket返回后,与listenfd关联的文件表项的引用计数为1,accept返回后,与connfd关联的文件表项的引用计数也是1. 然而fork返回后 这两个描述符在父进程和子进程之间共享(也就是被复制),所以这两个套接字相关联的文件表项的引用计数为2,这样父进程关闭connfd,在listenfd继续等待下一个连接,而子进程关闭listenfd,去connfd处理请求去,处理完成之后关闭connfd,该套接字才真正的清理和资源释放。
也就是说只有计数为0时,才会发送FIN报文,然后正常的TCP连接终止序列。如果我们确实想在某个TCP连接上发送一个FIN,可以改用shutdown函数代替close。
后续持续更新~