网络
ip
port:定位到主机的某一个进程,通过这个端口进程就可以接收到对应的网络数据了。unsigned int (0 ~ 2^`16-1)
头文件:sys/socket.h
字节序
字节序:字节的顺序,单个字节没有这个概念。字符串是字符的结合,也没有字节序问题。字符也没有字节序问题。
目前在各种体系的计算机中通常采用的字节存储机制主要有两种:Big-Endiam(大端,网络) 和 Little-Endian(小端,主机)。发送时:小端转大端。 接受时:大端转小端。
-
Little-Endian:数据的低位字节存储到内存的低地址为,数据的高位存储到内存的高地址位。(低低高高)
-
Big-Endiam:数据的低位字节存储到内存的高地址为,数据的高位存储到内存的低地址位。(低高高低)
-
套接字通信过程操作的数据都是大端存储的,包括:接受/发送的数据、ip地址、端口
大小端转换函数
ip地址转换
头文件#include <arp/inet>
/*
af:地址族(ipv4 ipv6)
- AF_INET:ipv4格式的地址
- AF_INET6:ipv6格式的地址
src:传入的参数,对应要转换的点分十进制的ip地址
dst:传出的参数,函数调用完成,转换得到大端整形ip被写入到这块内存中
返回值:成功返回1,失败返回0或-1
*/
int inet_pton(int af, const char* src, void *dst);
/*
af:地址族(ipv4 ipv6)
- AF_INET:ipv4格式的地址
- AF_INET6:ipv6格式NULL的地址
src:传入的参数,这个指针指向内存中的大端整形ip地址
dst:传出参数,存储转换得到的小端的点分十进制的ip地址
size:修饰dst参数,标记dst指向的内存最多可以有多少个字节
返回值:
- 成功:返回指针指向第三个参数对应的内存地址,通过返回值也可以直接取出转换得到的ip字符串
- 失败:返回NULL
*/
const char* inet_ntop(int af, const void* src, char* dst, soklen_t size);
// 点分十进制 转 大端整形
in_addr_t inet_addr(const char* cp);
// 大端整形 转 点分十进制
char* inet_ntoa(struct in_addr in);
TCP通信流程 特点
特点:面向连接、安全的流式传输协议。
流程:
服务器通信流程:
-
创建一个用于监听的套接字,这个套接字是一个文件描述符
int lfd = socket();
-
将监听的文件描述符和本地ip端口进行绑定
bind();
-
设置监听(成功之后开始监听,监听的是客户端的链接)
listen();
-
等待并接受客户端的请求,建立新的链接,会得到新的一个文件描述符(通信的),没有新连接请求就会阻塞
int cfd = accept();
-
通信,读写操作默认都是阻塞
read(); // 接受 write();//发送
-
断开链接,关闭套接字
close();
客户端的通信流程
-
创建一个通信的套接字
int cfd = socket();
-
连接服务器,需要知道服务器绑定的ip和端口
connect();
-
通信
read(); // close();
-
断开连接
close();
套接字函数
在终端用 man 函数名
查看文档
sockt通信案例
简单收发:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h> // 包含了sys/socket.h 头文件
int main() {
// 1.创建监听的套接子
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
// 2.绑定本地的ip port
struct sockaddr_in saddr; // 初始化为 struct sockaddr_in,然后在传参数的时候强制转换到 struct sockaddr
// 初始化
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0,对于0来说,大端和小端没有区别,可以绑定本地的任何个一个ip地址。会自动区读取网卡的实际ip地址。
int ret = bind(fd, (struct sockaddr*) &saddr, sizeof(saddr));
if (ret == -1) {
perror("bind");
return -1;
}
// 3.设置监听
ret = listen(fd, 128);
if (ret == -1) {
perror("listen");
return -1;
}
// 4. 阻塞等待客户连接
struct sockaddr_in caddr;
int addrlen = sizeof(caddr);
int cfd = accept(fd, (struct sockaddr*) &caddr, &addrlen);
if (cfd == -1) {
perror("accept");
return -1;
}
char ip[32];
printf("客户端的ip:%s, 端口号:%d\n", inet_ntop(AF_INET, &caddr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(caddr.sin_port));
// 5. 通信
while (1)
{
char buff[1024];
int len = recv(cfd, buff, sizeof(buff), 0);
if (len > 0) {
printf("client says: %s\n", buff);
send(cfd, buff, len, 0);
} else if (len == 0) {
printf("client已经断开连接\n");
break;
} else {
perror("recv");
break;
}
}
// 6.关闭所有的文件描述符
close(fd);
close(cfd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h> // 包含了sys/socket.h 头文件
int main() {
// 1.创建用于通信的套接子
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
// 2.连接服务器ip port
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.137.129", &saddr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*) &saddr, sizeof(saddr));
if (ret == -1) {
perror("socket");
return -1;
}
// 3. 进行通信
char buff[1024];
int number = 0;
while (1) {
sprintf(buff, "你好, hello world,%d..\n", number++);
send(fd, buff, strlen(buff) + 1, 0);
// 接受
memset(buff, 0, sizeof(buff));
int len = recv(fd, buff, sizeof(buff), 0);
if (len > 0) {
printf("server says: %s\n", buff);
} else if (len == 0) {
printf("服务器已经断开连接\n");
break;
} else {
perror("recv");
break;
}
sleep(2);
}
close(fd);
return 0;
}
服务器并发
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h> // 包含了sys/socket.h 头文件
#include <pthread.h>
// 信息结构体
struct SockInfo{
struct sockaddr_in addr; //地址信息
int fd; // 文件描述符
};
// 存储信息
struct SockInfo infos[512];
void* working(void* args);
int main() {
// 1.创建监听的套接子
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
// 2.绑定本地的ip port
struct sockaddr_in saddr; // 初始化为 struct sockaddr_in,然后在传参数的时候强制转换到 struct sockaddr
// 初始化
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0,对于0来说,大端和小端没有区别,可以绑定本地的任何个一个ip地址。会自动区读取网卡的实际ip地址。
int ret = bind(fd, (struct sockaddr*) &saddr, sizeof(saddr));
if (ret == -1) {
perror("bind");
return -1;
}
// 3.设置监听
ret = listen(fd, 128);
if (ret == -1) {
perror("listen");
return -1;
}
// 4. 阻塞等待客户连接
int addrlen = sizeof(struct sockaddr_in);
// 初始化结构体数组
int max = sizeof(infos) / sizeof(infos[0]);
for(int i = 0; i < max; i++) {
bzero(&infos[i], sizeof(infos[i]));
infos[i].fd = -1; // -1表示无效值,当前的fd是不可以用的,可以申请这个元素
}
while (1)
{
struct SockInfo* pinfo = NULL;
for(int i = 0; i < max;i ++) {
if(infos[i].fd == -1) {
pinfo = &infos[i];
break;
}
}
int cfd = accept(fd, (struct sockaddr*) &pinfo->addr, &addrlen);
pinfo->fd = cfd; //保存文件标识符
if (cfd == -1) {
perror("accept");
break; // 放弃连接
// continue; //冲连
}
char ip[32];
printf("客户端的ip:%s, 端口号:%d\n", inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(pinfo->addr.sin_port));
// 创建子线程
pthread_t tid;
pthread_create(&tid, NULL, working, pinfo);
pthread_detach(tid); //子线程与主线程相分离
}
close(fd);
return 0;
}
// 子线程
void* working(void* args) {
struct SockInfo* pinfo = (struct SockInfo*) args;
// 5. 通信
while (1)
{
char buff[1024];
int len = recv(pinfo->fd, buff, sizeof(buff), 0);
if (len > 0) {
printf("client says: %s\n", buff);
send(pinfo->fd, buff, len, 0);
} else if (len == 0) {
printf("client已经断开连接\n");
break;
} else {
perror("recv");
break;
}
}
// 6.关闭文件描述符
close(pinfo->fd);
pinfo->fd = -1; // 记录已经回收的元素
return 0;
}