Socket基础以及相关应用函数
什么是socket
socket,也就是我们所谓的套接字,就像插座一样。
IP地址可以在网络环境中唯一的标识一台主机
端口号:在主机当中唯一标识一个进程。
因此,IP地址+端口号,就可以在网络环境中唯一标识一个进程,而在网络环境中被唯一标识的这个进程,就是socket。
再说这个套接字,它本质上是Linux操作系统当中文件的一种类型。确切而言,它是一种伪文件,不占用存储。
再说,linux中实际占用存储空间的文件类型有三种:
- 普通文件
- 目录
- 软连接,内部记录所指向文件的文件的路径名。
除此之外,还有四种文件:
- 字符设备
- 块设备
- 管道
- 套接字
说到管道,它有一个读段和一个写端。而对应到管道,我们来分析socket,这个socket也会建立一个文件描述符,而它的特别之处在于,它有两个缓冲区:一个负责读入,一个负责写出。因此,socket是全双工双向通信的。
从上面的通讯原理示意,可知道套接字都是成对出现的。
相关的应用函数
IP地址转换函数
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
Sockaddr数据结构:
struct sockaddr {sa_family_t sa_family; /*address family, AF_xxx */char sa_data[14]; /*14 bytes of protocol address */
};
Pv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。
socket函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int socket(int domain,int type, int protocol);
domain:
-
AF_INET, 这式大多数用来产生socket的协议,使用TCP或者UDP来传输,使用IPv4地址。
-
AF_INET6,类似,使用IPV6协议
-
AF_UNIT,本地协议,使用在Unix或者Linux上,一般式服务器与客户端在同一机器上时候使用
type也就是使用的协议类型:
- 流式协议,默认方式tcp,SOCK_STREAM,这个协议是按照顺序的、可靠的、数据完整的
- 报式协议,默认方式udp,SOCK_DGRAM,这个协议是无连接的,固定长度的传输。
protocal: 传0表示使用默认协议。
bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd,文件描述符;
addr,构建出IP地址加端口号
addrlen,sizeof(addr)
返回值,成功0,失败为-1,errno
**服务器程序所监听的网络地址和端口号通常是固定不变的,**客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。
因此,bind的作用是将sockfd和addr绑定到仪器,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8keZSPrU-1578983102870)(image.png)]
listen函数
listen声明sockfd处于监听状态,并指明同时最多能与我建立连接的客户端数量。
Accept函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PgDx6GH-1578983102871)(image.png)]
以下程序,客户端连接服务器端,客户端输入字符串,服务器端将其转换为大写字符,然后客户端读入显示转换之后的字符串。在server中,有两层的while循环,第一层实现端口连接的监听,结合子线程的使用,在子线程中执行服务器的服务任务。
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
const int MAXLINE = 80;
const int SERV_PORT= 6666;
const char serv_addr[] = "127.0.0.1";
int main(int argc, char *argv[])
{struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n, len;
sockfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, serv_addr, &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {/_从标准输入获取数据_/printf("Now please input string: \n");fgets(buf, sizeof(buf), stdin);/_将数据写给服务器_/write(sockfd, buf, strlen(buf)); //写个服务器/_从服务器读回转换后数据_/len = read(sockfd, buf, sizeof(buf));printf("The data from server: \n");/_写至标准输出_/write(STDOUT_FILENO, buf, len);
}
close(sockfd);
return 0;}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(void)
{
int sfd, conn_fd;
pid_t pid;
int len, i,ret;
char buf[BUFSIZ], clie_IP[BUFSIZ];struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;/_创建一个socket 指定IPv4协议族 TCP协议_/
sfd = socket(AF_INET, SOCK_STREAM, 0);/_初始化一个地址结构 man 7 ip 查看对应信息_/
bzero(&serv_addr, sizeof(serv_addr)); //将整个结构体清零
serv_addr.sin_family = AF_INET; //选择协议族为IPv4
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
serv_addr.sin_port = htons(SERV_PORT); //绑定端口号 /_绑定服务器地址结构_/
bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));/_设定链接上限,注意此处不阻塞_/
listen(sfd, 64); //同一时刻允许向服务器发起链接请求的数量printf("wait for client connect ...\n");/_获取客户端地址结构大小_/
clie_addr_len = sizeof(clie_addr_len);
/_参数1是sfd; 参2传出参数, 参3传入传入参数, 全部是client端的参数_/printf("client IP:%s\tport:%d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port));while (1)
{conn_fd = accept(sfd, (struct sockaddr _)&clie_addr, &clie_addr_len); /_监听客户端链接, 会阻塞*/if(conn_fd < 0){perror("accept: ");}printf("accept a new client, ip: %s\n", inet_ntoa(clie_addr.sin_addr));if((pid = fork()) == 0) //创建子进程处理刚刚接收的连接请求{while (1){/_读取客户端发送数据_/len = read(conn_fd, buf, sizeof(buf));write(STDOUT_FILENO, buf, len);/_处理客户端数据_/for (i = 0; i < len; i++)buf[i] = toupper(buf[i]);/_处理完数据回写给客户端_/write(conn_fd, buf, len); }close(sfd);exit(0); //结束子进程 }else{close(conn_fd); //父进程关闭刚刚接收的连接请求,执行accept等待其他连接请求}
}
/_关闭链接_/
close(sfd);
close(conn_fd);
return 0;}