一切皆socket!本文介绍socket基础,socket的基本操作,并对socket中的TCP过程说明,还有一个例子用于实践。
I、socket的基本操作
socket是基于UNIX哲学中的“一切皆文件”的特例,采用“open-write/read-close”模式实现,我们一般说的socket编程,都是指的TCP编程。
1.1 socket()函数
int socket(int domain, int type, int protocol);
1、socket()
函数用于对应文件的打开操作,创建一个socket描述符,唯一标识所创建的socket。
2、socket()
函数的三个参数分别为:
· domain:标识协议域,又称为协议族(family)。常用的协议族有AF_INET,AF_INET6, AF_LOCAL等,这个参数决定了socket的地址类型,如AF_INET为我们最常用的协议族,其是采用ipv4地址(32bits)与端口号(16bits)的组合;
· type:指定socket类型,最常用的就是SOCK_STREAM,这是TCP中以流的方式传输类型;
· protocol:指定协议,常用的有IPPROTO_TCP,IPPROTO_UDP,分别对应TCP传输协议、UDP传输协议,当protocol为0时,会自动选择类型type类型对应的默认协议。
1.2 bind()函数
1、bind()
函数用于把一个地址族中的特定地址赋给socket。实现socket描述符与地址的绑定:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2、三个参数:
· sockfd:为socket()
函数得到的socket描述符;
· addr:一个const struct sockaddr*
指针,指向要绑定的sockfd的协议地址。下面是ipv4对应的结构:
struct sockaddr_in {
sa_family_t sin_family; // address family:AF_INET
in_port_t sin_port; // port in network byte order
struct in_addr sin_addr; // internet address;
};
struct in_addr {
uint32_t s_addr; // address in network byte order
};
· addrlen: 对应的地址长度。
通常服务器在启动的时候会绑定一个众所周知的地址(IP地址+端口号),用于提供服务,客户端可以通过它来连接服务器;而客户端就不用指定,由系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind()
,而客户端就不用调用,而是在connect()
时由系统随机生成一个。
1.3 listen(), connect()函数
作为服务器,在调用bind()
完成绑定之后,就会调用listen()
来监听这个已经绑定好地址和端口的socket,将socket()
创建的主动socket转化为监听socket。
int listen(int sockfd, int backlog);
之后,客户端可以调用connect()
发出连接请求,服务器就可以接受这个请求:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect()
的第一个参数为客户端的socket,第二个参数为服务器的地址,第三个参数为服务器地址长度。
1.4 accept()函数
服务器在返回listen()
函数之后,所的到的监听socket,在收到一个connect()
时会调用accept()
函数接受connect请求:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第一个参数为服务器socket, 第二个参数用于返回客户端的协议地址,第三个参数为地址长度。如果accept()
成功,则返回值为内核生成的一个已连接socket,用以处理这个连接。
一个服务器通常仅仅只创建一个监听socket,在服务器的生存周期一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket,当服务器完成这个服务之后,相应的已连接socket就被关闭。
1.5 read(), write()等函数
客户端与服务器建立好连接之后。可以调用网络I/O进行读写操作了,网络I/O操作由下面几组:
· read()/write()
· recv()/send()
· readv()/writev()
· recvmsg()/sendmsg()
· recvfrom()/sendto()
推荐使用recvmsg()/sendmsg()
函数,这是最通用的I/O函数,实际上上面的其他函数都可以替换成这两个函数。它们的声明如下:
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sento(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read()
函数是负责从fd中读取内容到buf中,返回所读到的字节数。
write()
函数将buf中的字节内容拷贝到fd上,返回成功拷贝的字节数。
1.6 close()函数
服务器可以使用close()
函数关闭响应的socket:
#include<unistd.h>
int close(int fd);
close是将对应的socket引用计数-1, 只有当引用计数为0的时候,才会触发客户端向服务器发送终止连接请求。
II、socket中TCP三次握手与四次挥手
关于三次握手与四次挥手已经在wenmingxing TCP三次握手与四次挥手中详细说明,下面只介绍各个函数在每个阶段所起的作用。
2.1 三次握手
图1展示了TCP三次握手的过程,以及编程函数所出现的阶段:
图1、socket中的三次握手1、客户端调用connect()
触发第一次握手,此时connect()
阻塞,直到收到服务器返回ACK。
2、服务器收到第一次握手SYN包之后,调用accept()
并阻塞,直到收到第三次握手的ACK,accept()
返回。三次握手完成,连接建立。
2.2 四次挥手
图2展示了TCP四次挥手的过程,以及编程函数所出现的阶段:
图2、socket中的四次挥手III、一个服务器与客户端实例
3.1 服务器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#define MAXLINE 4096
int main() {
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
int n;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("socket error");
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr)); //将servaddr全部初始为0,完成初始化
//为servaddr赋值
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY为0.0.0.0,设置IP 地址的时候使用htonl
servaddr.sin_port = htons(6666); //设置端口为6666,使用htons
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
printf("bind error");
exit(0);
}
if (listen(listenfd, 10) == -1) {
printf("listen error");
exit(0);
}
printf("===============waiting for client's request=================\n");
while (1) {
if ((connfd == accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1) {
printf("accept error");
continue;
}
n = recv(connfd, buff, MAXLINE, 0); //相当于将connfd中接收到的东西copy到buff中
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
close(connfd); //关闭这个已连接的socket
}
close(listenfd); //最后关闭唯一的监听socket
}
3.2 客户端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#define MAXLINE 4096
int main(int argc, char** argv) {
int sockfd, n;
char recvline[MAXLINE], sendline[MAXLINE];
struct sockaddr_in servaddr;
if (argc != 2) {
printf("paraments error");
exit(0);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("socket error");
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
printf("inet_pton error");
exit(0);
}
printf("==========send msg to server=============\n");
//fgets(sendline, MAXLINE, stdin);
scanf("%s", sendline);
if (send(sockfd, sendline, strlen(sendline), 0) < 0) {
printf("send error");
exit(0);
}
close(sockfd);
exit(0);
}
【参考】
[1] Linux Socket编程
[2] 《深入理解计算机系统》
欢迎转载,转载请注明出处wenmingxing socket编程
网友评论