C语言网络编程——基础
OSI七层参考模型是理想化的并没有完全实现的模型。应用层提供响应的应用服务表示层数据的表示和加密会话层建立会话关系传输层通过传输协议传输数据网络层实现跨子网通信,路由转发,维护路由表。数据链路层实现以太网内数据帧的转发物理层按照一定的传输规则传输电信号。TCP/IP 四层模型详细介绍见https://blog.csdn.net/Stars____/article/details/108694074
·
OSI七层参考模型
是理想化的并没有完全实现的模型。
应用层
提供响应的应用服务
表示层
数据的表示和加密
会话层
建立会话关系
传输层
通过传输协议传输数据
网络层
实现跨子网通信,路由转发,维护路由表。
数据链路层
实现以太网内数据帧的转发
物理层
按照一定的传输规则传输电信号。
TCP/IP 四层模型
详细介绍见https://blog.csdn.net/Stars____/article/details/108694074
SCTP协议:TCP的升级版
TCP/IP 网络编程
unix域套接字:用于本地进程间的通信。
-
Socket:是一个特殊的文件描述符。是一种通用的网络编程的接口。
- 在OSI模型中处于会话层和传输层之间。
- 在TCP/ IP模型中处于应用层和传输层之间。
int socket(int family, int type, int protocol);
流式套接字(TCP)、数据包套接字(UDP)
-
IP地址
- ipv4转换函数有inet_aton()、inet_addr()和inet_ntoa()
- ipv4和ipv6兼容的函数有inet_pton()和inet_ntop()
- 将strptr转换位网络字节序二进制值,仅适用于IPV4,此函数不能用于255.255.255.255的转换。
- pton能正确的处理255.255.255.255的转换问题。
- pton的逆用
- sa_family结构体定义在 #include <netinet/in.h> 中。
-
端口
-
字节序
- 本地字节序和网络字节序是不一样的。所以需要转换。
- 字节序转换涉及4个函数:htons()、ntohs()、htonl()、ntohl()。h代表host,n代表network,s代表short,l代表long
- bind():该函数将保存在相应地址结构中的地址信息与套接字进行绑定。它主要用于服务器端,客户端创建的套接字可以不绑定地址。
- listen():在服务端程序成功建立套接字并与地址进行绑定后,通过调用listen函数将套接字设置成监听模式(被动的),准备接收客户端的连接请求。
- accept():服务端通过调用accept函数等待并接受客户端的连接请求。建立好TCP连接后,该函数会返回一个新的已连接套接字。
- connect():客户端通过该函数向服务器端的监听套接字发送连接请求。
- send()和recv():用于TCP和UDP通信过程中发送和接收数据。
- sendto()和recvfrom():这两个函数一般用与UDP中的发送和接收,当用在TCP中时后面与地址有关的参数不起作用。函数作用等同于send()和recv()
- 通用结构体:
struct sockaddr{
sa_family_t sa_family; //协议
char sa_data[14];
};
- AF_INET定义结构体
struct sockaddr_in{
sa_family_t sin_family; // 协议
in_port_t sin_port; // 端口号的网络字节序
struct in_addr sin_addr;//
};
struct in_addr{
uint32_t s_addr; // IP地址 网络字节序
};
服务端
#include "net.h"
#define BUFF_SIZE 128
int main(int argc, char* argv[])
{
int listenfd, connfd;
struct sockaddr_in servaddr, cliaddr;
socklen_t peerlen;
char buf[BUFF_SIZE];
if(argc < 3){
printf("入参有误!\n");
return -1;
}
// 建立socket连接
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
return -1;
}
printf("listenfd = %d\n", listenfd);
// 设置sockaddr_in 结构体中的相关参数
bzero(&servaddr, sizeof(servaddr)); // 擦除内存 全写0
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
// 绑定函数
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){
perror("bind");
return -1;
}
printf("bind success!\n");
// 调用listen()函数,设置监听模式
if(listen(listenfd, 10) == -1){
perror("listen");
return -1;
}
printf("listening...\n");
// 调用accept函数,等待客户端的连接
peerlen = sizeof(cliaddr);
while(1){
if((connfd = accept(listenfd,(struct sockaddr*)&cliaddr, &peerlen)) < 0){
perror("accept");
return -1;
}
bzero(buf, sizeof(buf));
if(recv(connfd, buf, BUFF_SIZE, 0) == -1){
perror("recv");
return -1;
}
printf("Received a message: %s\n", buf);
strcpy(buf, "Welcome to server");
send(connfd, buf, BUFF_SIZE, 0);
close(connfd);
}
close(listenfd);
return 0;
}
客户端
#include "net.h"
#define BUFF_SIZE 128
int main(int argc, char* argv[])
{
int sockfd;
char buf[BUFF_SIZE] = "Hello server";
struct sockaddr_in servaddr;
if(argc < 3){
printf("入参有误!\n");
return -1;
}
// 创建socket
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
return -1;
}
// 设置sockaddr_in结构体中相关参数
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
// 调用connect函数向服务器发送连接请求
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
perror("connect");
return -1;
}
// 发送消息给服务端
send(sockfd, buf, sizeof(buf), 0);
if(recv(sockfd, buf, BUFF_SIZE, 0) == -1){
perror("recv");
return -1;
}
printf("recv from server : %s\n", buf);
close(sockfd);
return 0;
}
TCP服务端客户端流程图
一个服务器服务多个客户端的TCP编程
主要还是对服务器端的优化。
-
循环服务器:分时处理客户端,直到前一个客户端退出后新的客户端才能被服务器响应
-
并发服务器:常用多线程实现,进程太过于耗费资源不常用
IO多路复用
在上述并发服务器中使用多线程能满足多个客户端的连接,但是客户端的数量上不能太多,假设有1000个客户端要连接这个服务器,那服务器就得开1000个线程,而且并不是每个客户端都是活跃的,对于那些不活跃的客户端服务器也不能释放线程资源,直到客户端断开连接,这样对服务器来说压力太大。
所以就有了IO多路复用。
多路复用IO
下一篇详细讲!
更多推荐
已为社区贡献2条内容
所有评论(0)