网络编程 day02
- 6. 字节序
- 端口转换
- IP转换
- 7. TCP编程
- 1. 流程
- 2. 函数接口
- socket
- bind
- listen
- accept
- recv
- connect
- send
- 4. 通信代码优化过程
- 5. 最终通信代码
- 8. 粘包
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;
}