(一) socket编程基础知识
学习socket编程前,我们先来提升一下认知:
(1) 地址
地址由IP地址、端口号构成
- IP地址:用于设备标识
- 端口号:用于标识网络服务(ftp、http、socket) (5000到10000之间选择)
IP地址转换API(IP地址是字符串要转换成网络能识别格式)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
字节序转换API (端口号: 主机字节序转换成网络字节序)
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h --> host(主机) n --> net(网络) s --> short(小端) l --> long(大端)
(2) 传输协议
- TCP:面向连接 (先建立连接再发送数据)
- 可靠性强。用于传输精度高数据量小的交互通信,一对一交互,无差错,不丢失,不重复,且按序到达
- 全双工可靠信道
- UDP:面向报文 (无需连接直接发送数据)
- 传输能力强。用于传输精度要求不高数据量大的交互通信,会有数据丢失,支持一对一,一对多,多对一,多对多交互
- 不可靠信道
(二) socket编程步骤
步骤分为客户端和服务器端,详细步骤如下图示:
(三) socket编程API
(1) socket()函数
作用:获取套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
- domain
指定所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
- AF_INET ???IPv4 因特网
- AF_INET6 ? ? IPv6 因特网
- AF_UNIX ???Uinix域
- AF_ROUTE ??路由套接字
- AF_KEY ? ? ?密钥套接字
- AF_UNSPEC ? 未指定
- type
指定socket类型:
- SOCK_STREAM ?指定TCP协议
字节流套接字提供可靠的、面向连接的通信流;他使用TCP协议,从而保证了数据传输正确性和顺序性 - SOCK_DGRAM ? 指定UDP协议
数据报文套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,使用UDP协议 - SOCK_RAW ? ? 指定底层协议
允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用不便,主要用于一些协议的开发- 项目 - protocol
通常赋值为0
- 0 ? ? ?? ????选择type类型的默认协议TCP协议
- IPPROTO_TCP ? ?TCP传输协议
- IPPPOTO_UDP ? ?UDP传输协议
- IPPROTO_SCTP??SCTP传输协议
- IPPROTO_TIPC? ?TIPC传输协议
(2)bind()函数
功能:绑定IP号和端口号到socketfd
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
- sockfd
是一个socket描述符 - addr
是一个指向包含有本机IP地址及端口号等信息的struct sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同 - addrlen
结构体add的大小 - 返回值
成功返回0 ,失败返回-1,errno被设置
struct sockaddr ----IPv4
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
常用struct sockaddr_in类型强转替换struct sockaddr
struct sockaddr_in {
__kernel_sa_family_t sin_family;
__be16 sin_port;
struct in_addr sin_addr;
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr {
__be32 s_addr;
};
命令行查找struct sockaddr_in
- 进入头文件文件夹
cd /usr/include/
- 当前文件夹下显示行数不区分大小写递归查找
grep "struct sockaddr_in {" * -nir
- 进入头文件查看
vi linux/in.h +184
(3) listen()函数
功能:设置最大连接数,一直监听是否有其他的客户进程请求连接,响应连接TCP三次握手
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd
sockfd进行监听的套接字 - backlog
指定在请求队列中允许的最大请求数 - 返回值
成功返回0 ,失败返回-1,errno被设置
(4) accept()函数
功能:与客户端建立连接,保存客户端套接字对应的“地方”(包括客户端IP和端口信息等),如果已完成请求的队列为空将阻塞到下一个请求到来
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd
监听的套接字 - addr (可选择不使用填NULL)
用于保存已连接的客户端的地址 - addrled (可选择不使用填NULL)
客户端地址长度 - 返回值
返回一个已连接的新的套接字,交互完成后该套接字就会被关闭,第一个参数为监听套接字且服务器只创建一个 失败返回-1,errno被设置、
(5) 数据收发函数
收发函数有很多套可以使用:
- read()、write() 已连接状态使用
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
- recv()、send() 已连接状态使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
使用与read、write类似, flags控制选项常设置为0
- readv()、wirtev() 已连接状态使用
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base;
size_t iov_len;
};
iovcnt 为iov大小
- recvmsg()、sendmsg() 未连接状态可使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
struct msghdr {
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
size_t msg_iovlen;
void *msg_control;
size_t msg_controllen;
int msg_flags;
};
- recvfrom()、sendto() 未连接状态可使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
1、2、3套多用于TCP 4、5套多用于UDP
(6) connect()函数
功能:向服务端发出连接请求并进行连接
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd
与服务端连接的套接字 - addr
服务端IP地址与端口号地址结构体指针 - addrlen
常设置为 sizeof(struct sockaddr) - 返回值
成功返回0 ,失败返回-1,errno被设置
(四) demo
socket编程实现多方收发信息 需要注意的是:
- 根据TCP内部算法,发送端发送内容为空的时候,不会发送,但是会往下执行;接收端被阻塞,所以需要进行一个是否为空的判断
- 当主动关闭的一端断开连接的时候(不管是CTRL+C,还是调用close()方法等),主动关闭的一端都会发送FIN包给另一端,而被动关闭的一端再收到FIN包之后,不断接收到0字节,不做为空判断并处理,就会出现刷屏现象
服务端
#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main()
{
int s_fd = 0;
int ret = 0;
int c_fd = 0;
int n_read;
int n_write;
char w_buf[128];
char r_buf[128];
struct sockaddr_in addr;
struct in_addr sin_addr;
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if (s_fd == -1){
perror("socket ");
exit(-1);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
inet_aton("192.168.1.8", &addr.sin_addr);
ret = bind(s_fd, (const struct sockaddr*)&addr, sizeof(addr));
if (ret == -1){
perror("bind ");
exit(-1);
}
listen(s_fd, 10);
int addrlen = 0;
struct sockaddr_in s_addr;
addrlen = sizeof(s_addr);
while(1){
c_fd = accept(s_fd, (struct sockaddr*)&s_addr, &addrlen);
if (c_fd == -1){
perror("accept ");
exit(-1);
}
printf("get connect IP: %s\n", inet_ntoa(s_addr.sin_addr));
if (fork() == 0){
write(c_fd, "welcome to server!", strlen("welcome to server!"));
if (fork() == 0){
while(1){
memset(r_buf, 0, 128);
n_read = read(c_fd, r_buf, sizeof(r_buf));
if (n_read == -1){
perror("read ");
} else if (n_read == 0){
printf("IP :%s clinet quit!\n", inet_ntoa(s_addr.sin_addr));
exit(0);
} else {
printf("IP: %s %s\n", inet_ntoa(s_addr.sin_addr), r_buf);
}
}
} else {
while(1){
gets(w_buf);
n_write = write(c_fd, w_buf, sizeof(w_buf));
if (n_write == -1){
perror("write ");
}
}
}
}
}
return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int s_fd = 0;
int ret = 0;
char r_buf[128];
char w_buf[128];
int n_read;
int n_write;
struct sockaddr_in addr;
if (argc != 3){
printf("input false!\n");
exit(-1);
}
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if (s_fd == -1){
perror("socket ");
exit(-1);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &addr.sin_addr);
ret = connect(s_fd, (const struct sockaddr*)&addr, sizeof(addr));
if (ret == -1){
perror("connect ");
exit(-1);
}
write(s_fd, "hi server!", strlen("hi server!"));
if (fork() == 0){
while(1){
memset(r_buf, 0, 128);
n_read = read(s_fd, r_buf, sizeof(r_buf));
if (n_read == 0){
printf("server quit!\n");
exit(0);
} else if (n_read == -1){
perror("read ");
} else{
printf("get message: %s\n", r_buf);
}
}
} else {
while(1){
scanf("%s", w_buf);
n_write = write(s_fd, w_buf, sizeof(w_buf));
if (n_write == -1){
perror("write ");
}
}
}
return 0;
}
widow命令行telnet
telnet 192.168.1.8 8888
运行结果
|