2.Socket编程
2.1 Socket编程基础
Socket
- 网络通信的函数接口
- 封装了传输层协议
– TCP
– UDP
2.1.1 Socket套接字
创建一个套接字得到的是一个文件描述符
套接字
- 创建成功,得到一个文件描述符
fd
fd
操作的是一块内核缓冲区默认是阻塞状态
套接字通讯原理示例:
2.1.2 网络字节序
小端法:高位存高地址、低位存低地址
大端法:高位存低地址、低位存高地址
网络数据流采用大端字节序
网络字节序和主机字节序的转换:
#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);
IP地址转换函数:
#include <arpa/inet.h>
/*
* 函数功能:
* 将本地字节序IP地址转换为网络字节序IP地址
* 参数:
* af:IP地址类型(AF_INET、AF_INET6)
* src:IP地址(点分十进制)
* dst:转换后的网络字节序IP地址(传出)
* 返回值:
* 成功:1
* 异常:0(无效IP地址)
* 失败:-1
*/
int inet_pton(int af, const char *src, void *dst);
/*
* 函数功能:
* 将网络字节序IP地址转换为本地字节序IP
* 参数:
* af:IP地址类型(AF_INET、AF_INET6)
* src:网络字节序IP地址
* dst:转换后的本地字节序IP地址(传出)
* size:dst大小
* 返回值:
* 成功:dst
* 失败:NULL
*/
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
2.2 Socket相关函数
2.2.1 sockaddr数据结构
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
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 */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */
__be16 sin6_port; /* Transport layer port # */
__be32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
2.2.2 建立一个socket通信socket()
#include <sys/types.h>
#include <sys/socket.h>
/*
* 函数功能:
* 建立一个socket通信
* 参数:
* domain:IP地址类型(常用:AF_INET/AF_INET6/AF_UNIX)
* type:
* SOCK_STREAM 提供基于连接双向连续且可信赖的数据流,即TCP
* SOCK_DGRAM 使用不连续不可信的数据报连接
* protocol:type的代表协议,通常为0
* 返回值:
* 成功:新的fd
* 失败:-1, 并设置errno
*/
int socket(int domain, int type, int protocol);
/* 示例 */
socket(AF_INET, SOCK_STREAM, 0);
2.2.3 绑定socketbind()
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
/*
* 函数功能:
* 给socket绑定一个地址结构(IP+Port)
* 参数:
* sockfd:socket()返回的socket文件描述符
* addr:(struct sockaddr *)&addr(地址结构,传入)
* struct sockaddr_in addr;
* addr.sin_family = AF_INET;
* addr.sin_port = htons(port_num);
* addr.sin_addr.s_addr = htonl(INADDR_ANY);
* htonl(INADDR_ANY) 取出系统中有效的任意IP地址(二进制类型)
* addrlen:addr的大小
* 返回值:
* 成功:0
* 失败:-1, 并设置errno
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2.2.4 设置连接上限listen()
#include <sys/types.h>
#include <sys/socket.h>
/*
* 函数功能:
* 设置同时与服务器建立连接的上限数
* 参数:
* sockfd:socket()返回的socket文件描述符
* backlog:上限数值(max=128)
* 返回值:
* 成功:0
* 失败:-1, 并设置errno
*/
int listen(int sockfd, int backlog);
2.2.5 服务器建立连接accept()
#include <sys/types.h>
#include <sys/socket.h>
/*
* 函数功能:
* 阻塞等待客户端建立连接
* 参数:
* sockfd:socket()返回的socket文件描述符
* addr:成功与服务器建立连接的那个客户端的地址结构(传出)
* addrlen:
* 传入:addr的大小
* 传出:客户端addr的实际大小
* 返回值:
* 成功:与客户端建立连接的socket文件描述符
* 失败:-1, 并设置errno
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
2.2.6 客户端建立连接connect()
#include <sys/types.h>
#include <sys/socket.h>
/*
* 函数功能:
* 客户端与服务器建立连接
* 参数:
* sockfd:socket()返回的socket文件描述符
* addr:服务器的地址结构(传入)
* addrlen:addr的大小
* 返回值:
* 成功:0
* 失败:-1, 并设置errno
* 如果不使用bind()绑定客户端地址结构,采用“隐式绑定”
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2.3 Socket创建流程
2.3.1 Server服务器端
Socket TCP Server:
- 创建套接字
–int lfd = socket()
- 绑定本地IP和端口
–strcut sockaddr_in serv;
–serv.sin_family = AF_INET/AF_INET6
–ser.sin_port = htons(port);
–sserv.sin_addr.s_addr = htonl(INADDR_ANY)
–bind(lfd, &serv, sizeof(serv));
- 设置监听上限
–listen(lfd, 128);
- 等待接受连接请求
–struct sockaddr_in client;
–int len = sizeof(client);
–int cfd = accept(lfd, &client, &len);
–cfd
用于通信- 通信
– 接收数据:read()
– 发送数据:write()
- 关闭
–close(lfd);
–close(cfd);
Server示例代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
int main(int argc, const char *argv[]) {
// 1. 创建监听的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
perror("socket error");
exit(1);
}
// 2. lfd 和本地的IP port绑定
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET; // 地址族协议 - ipv4
server.sin_port = htons(8888); // 设置端口
server.sin_addr.s_addr = htonl(INADDR_ANY); //设置IP地址
int ret = bind(lfd, (struct sockaddr *) &server, sizeof(server));
if (ret == -1) {
perror("bind error");
exit(1);
}
// 3. 设置监听
ret = listen(lfd, 128);
if (ret == -1) {
perror("listen error");
exit(1);
}
// 4. 等待并接收连接请求
struct sockaddr_in client;
socklen_t len = sizeof(client);
int cfd = accept(lfd, (struct sockaddr *) &client, &len);
if (cfd == -1) {
perror("accept error");
exit(1);
}
printf(" accept successful !!!\n");
char ipbuf[64] = {0};
printf("client IP: %s, port: %d\n",inet_ntop(AF_INET, &client.sin_addr.s_addr,ipbuf, sizeof(ipbuf)),
ntohs(client.sin_port));
// 5. 一直通信
while (1) {
// 先接收数据
char buf[BUFSIZ] = {0};
int len = read(cfd, buf, sizeof(buf));
if (len == -1) {
perror("read error");
exit(1);
} else if (len == 0) {
printf(" 客户端已经断开了连接 \n");
close(cfd);
break;
} else {
printf("recv buf: %s\n", buf);
// 转换 - 小写 -> 大写
for (int i = 0; i < len; ++i) {
buf[i] = toupper(buf[i]);
}
printf("send buf: %s\n", buf);
write(cfd, buf, len);
}
}
// 6. 关闭
close(lfd);
return 0;
}
服务器端运行结果:
使用nc 127.0.0.1 8888
:连接服务器
2.3.2 Client客户端
客户端:
- 创建套接字
–int fd = socket()
- 连接服务器
–connect();
- 通信
– 接收数据:read()
– 发送数据:write()
- 断开连接
–close(fd);
Client示例代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#define SERV_PORT 8888
int main(int argc, const char* argv[])
{
// 1. 创建套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd == -1)
{
perror("socket error:");
exit(-1);
}
// 2. 连接服务器
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if( ret == -1 )
{
perror("connect error:");
exit(-2);
}
char buf[BUFSIZ] = { 0 };
while(1)
{
write(cfd, "hello", 5);
sleep(1);
ret = read(cfd, buf, sizeof(buf));
if(ret <= 0)
break;
write(STDOUT_FILENO, buf, ret);
}
close(cfd);
return 0;
}
2.4 数据收发
socket编程中
flags
一般为0
2.4.1 数据接收
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
2.4.2 数据发送
ssize_t write(int fd, const void *buf, size_t count);
ssize_t write(int sockfd, const void *buf, size_t len, int flags);
2.5 半关闭
A给B发送lFIN(调用了close函数),但是B没有给A发送FIN(B没有调用close)
A不能给B发送数据,A可以接收B发送的数据
函数:
int shutdown(int sockfd, int how);
sockfd:要半关闭的一方对应的文件描述符
how:
- SHUT_RD:0-read
- SHUT_WR:1-write
- SHUT_RDWR:2-read&write
2.6 查看网络状态信息netstat
参数 | 说明 |
---|---|
a(all) | 显示所有选项,默认不显示LISTEN相关信息 |
p | 显示建立相关链接的程序名 |
n | 拒绝显示别名,能显示数字的全部转化为数字 |
t(tcp) | 仅显示tcp相关选项 |
u(udp) | 仅显示udp相关选项 |
l | 仅列出有在Listen(监听)的服务状态 |
2.7 端口复用
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
# 设置方法、需要在绑定端口前设置
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));