网络编程 day02
6. 字节序
端口转换
主机字节序转换为网络字节序 (小端序->大端序)
#include <arpa/inet.h>
u_long htonl (u_long hostlong); // host to net long
u_short htons (u_short short); // host to net short
网络字节序转换为主机字节序(大端序->小端序)
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
IP转换
主机字节序转换为网络字节序 (小端序->大端序)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *strptr); //该参数是字符串
typedef uint32_t in_addr_t;
功能: 主机字节序转为网络字节序
参数: const char *strptr
: 字符串
返回值: 返回一个无符号长整型数(无符号32位整数用十六进制表示),
否则NULL
网络字节序转换为主机字节序(大端序->小端序)
char *inet_ntoa(stuct in_addr inaddr);
功能: 将网络字节序二进制地址转换成主机字节序。
参数: stuct in_addr in addr
: 只需传入一个结构体变量
返回值: 返回一个字符指针, 否则NULL;
注意:
struct in_addr
{
in_addr_t s_addr;
};
7. TCP编程
C/S:client/server 客户端与服务器
B/S:browser/server 浏览器与服务器
1. 流程
服务器
- 创建套接字——socket()
- 指定网络信息
- 绑定套接字——bind()
- 监听套接字——listen()
- 接收客户端连接请求——accept()
- 创建新的套接字——socket()
- 接收/发送消息——recv()/send()
- 关闭套接字——close()
客户端
- 创建套接字——socket()
- 指定服务器网络信息
- 连接请求——connect()
- 接收/发送消息——recv()/send()
- 关闭套接字——close()
2. 函数接口
socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
int domain
协议族
int type
套接字类型
int protocol
协议
返回值:成功返回套接字文件描述符,失败返回-1,更新errno
补充:
int domain
协议族
宏名 | 含义 |
---|---|
AF_UNIX, AF_LOCAL | 本地通信 |
AF_INET | ipv4 |
AF_INET6 | ipv6 |
int type
套接字类型
宏名 | 含义 |
---|---|
SOCK_STREAM | 流式套接字 |
SOCK_DGRAM | 数据报套接字 |
SOCK_RAW | 原始套接字 |
int protocol
协议:0 自动匹配底层
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定套接字
参数:
int sockfd
:套接字
const struct sockaddr *addr
:用于通信结构体,需要强转
socklen_t addrlen
:结构体大小
返回值:成返回0,失败返回-1,更新errno
补充:
struct sockaddr_in
{
sa_family_t sin_family; // ipv4协议
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IP地址
};
struct in_addr
{
uint32_t s_addr; // IP地址
};
listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:监听套接字,将主动套接字变为被动套接字
参数:
int sockfd
:连接套接字的文件描述符
int backlog
:同时响应客户端请求链接的最大个数
返回值:成功 0 失败-1,更新errno
补充:
参数:int backlog
:不能写0,一般写6
不同平台可同时链接的数不同
两个队列:
队列1:保存正在连接
队列2,连接上的客户端
accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞等待客户端的连接请求
参数:
int sockfd
:链接套接字
struct sockaddr *addr
: 客户端网络信息结构体,需要强转
socklen_t *addrlen
:客户端网络信息结构体的大小
返回值: 成功:通信套接字的文件描述符,失败:-1,更新errno
补充:
- 如果不需要关心具体是哪一个客户端,那么可以填NULL
- 获取的ip和端口号需要转换大小端
recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:
int sockfd
:用于通信的套接字文件描述符
void *buf
:存放通信内容
size_t len
: 获取的内容的大小
int flags
一般填0
,等价于read
MSG_DONTWAIT
非阻塞
返回值:
成功:接收的字节个数,为0时表示客户端退出
失败:< 0,更新errno
connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器
参数:
int sockfd
:通信用的套接字
const struct sockaddr *addr
:服务器网络信息
socklen_t addrlen
:结构体的大小
返回值:成功返回0,失败返回-1,更新errno
send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
int sockfd
通信用的套接字
const void *buf
存放通信内容
size_t len
发送的内容的大小
int flags
一般填0,等价于write
4. 通信代码优化过程
- 优化服务器代码,客户端链接成功后,可以循环多次通信,当客户端输入quit时,客户端退出。
- 优化服务器代码客户端输入quit退出后,服务器不退出,等待下一个客户端连接
循环服务器:一个服务器可以连接多个客户端,但是不能同时 - 地址和端口都通过参数传入
- 自动获取本机地址
- 增加来电显示功能:显示客户端连入的地址和端口
5. 最终通信代码
服务器
int main(int argc, char const *argv[])
{
// 变量定义
int fd_socket = -1; // 连接套接字文件描述符
struct sockaddr_in saddr; // 服务器网络信息
struct sockaddr_in caddr; // 客户端网络信息
int acc = -1; // 通信套接字的文件描述符
int len = sizeof(caddr); // 客户端网络信息结构体大小
char buf[128] = {}; // 存放收发的内容
int ret = -1; // 返回值
// 1. 创建套接字
fd_socket = socket(AF_INET, SOCK_STREAM, 0);
if (fd_socket < 0)
{
perror("socket err");
return -1;
}
printf("fd_socket : %d\n", fd_socket);
// 2. 指定网络信息
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = INADDR_ANY;
// 3. 绑定套接字
if (bind(fd_socket, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind ok\n");
// 4. 监听套接字
// 将主动套接字变成被动套接字
// 队列1:未连接
// 队列2:已连接
if (listen(fd_socket, 6) < 0)
{
perror("listen err");
return -1;
}
printf("listen ok\n");
while (1)
{
// 接收客户端连接请求
acc = accept(fd_socket, (struct sockaddr *)&caddr, &len);
if (acc < 0)
{
perror("accept err");
return -1;
}
printf("accfd : %d\n", acc);
printf("ip:%s\tport:%d\t", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
while (1)
{
// 接收消息
ret = recv(acc, buf, sizeof(buf), 0);
if (ret < 0)
{
perror("recv err");
return -1;
}
else if (ret == 0)
{
printf("client close\n");
break;
}
// 使用通信信息
printf("buf : %s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
// 关闭文件描述符
close(acc);
close(fd_socket);
return 0;
}
客户端
int main(int argc, char const *argv[])
{
int sockfd = -1; // 套接字文件描述符
char buf[128] = {}; // 通信内容
int ret = 0; // 返回值
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd : %d\n", sockfd);
// 指定服务器网络信息
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
// 连接请求
ret = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
if (ret)
{
perror("connect err");
return -1;
}
printf("connect ok\n");
// 发送消息
while (1)
{
fgets(buf, sizeof(buf), stdin);
if(buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
if(!strcmp(buf, "quit"))
{
printf("client close\n");
break;
}
send(sockfd, buf, strlen(buf), 0);
}
// 关闭文件描述符
close(sockfd);
return 0;
}