一、linux网络编程框架
1、网络是分层的
- OSI 7层模型
- 网络为什么要分层(网络是最复杂的通信)
- 网络分层的具体表现
2、TCP/IP协议引入
- TCP/IP协议是用的最多的网络协议实现
- TCP/IP分为4层,对应OSI的7层
- 我们编程时最关注应用层,了解传输层,网际互联层和网络接入层不用管
3、BS和CS
- CS架构介绍(client server,客户端服务器架构)
- BS架构介绍(broswer server,浏览器服务器架构)
二、TCP协议的学习1
1、关于TCP理解的重点
- TCP协议工作在传输层,对上服务socket接口(API),对下调用IP层
- TCP协议面向连接,通信前必须先3次握手建立连接关系后才能开始通信。
- TCP协议提供可靠传输,不怕丢包、乱序等。
2、TCP如何保证可靠传输
- TCP在传输有效信息前要求通信双方必须先握手,建立连接才能通信
- TCP的接收方收到数据包后会ack给发送方,若发送方未收到ack会丢包重传
- TCP的有效数据内容会附带校验,以防止内容在传递过程中损坏
- TCP会根据网络带宽来自动调节适配速率(滑动窗口技术)
- 发送方会给各分割报文编号,接收方会校验编号,一旦顺序错误即会重传。
三、TCP协议的学习2
1、TCP的三次握手
- 建立连接需要三次握手(三次单向通信)
- 建立连接的条件:服务器listen时客户端主动发起connect
2、TCP的四次握手(四次挥手,断开连接)
- 关闭连接需要四次握手
- 服务器或者客户端都可以主动发起关闭
- 注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管
3、基于TCP通信的服务模式
- 具有公网IP地址的服务器(或者使用动态IP地址映射技术)
- 服务器端socket、bind、listen后处于监听状态
- 客户端socket后,直接connect去发起连接。
- 服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,而且双方均可发起
- 双方均可发起关闭连接
4、常见的使用了TCP协议的网络应用
- http(超文本,图像等)、ftp(纯文本)
- QQ服务器
- mail服务器
四、socket编程接口介绍
1、建立连接
- socket:socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。
int socket(int domain, int type, int protocol); - bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); - listen
int listen(int sockfd, int backlog); - connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2、发送和接收
- send和write
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags); - recv和read
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t read(int fd, void *buf, size_t count);
3、辅助性函数
- inet_aton、inet_addr、inet_ntoa
in_addr_t inet_addr(const char *cp); - inet_ntop、inet_pton
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
int inet_pton(int af, const char *src, void *dst);
4、表示IP地址相关数据结构
-
都定义在 netinet/in.h
-
typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
-
struct in_addr
{
in_addr_t s_addr;
};- struct sockaddr_in
{
_SOCKADDR_COMMON (sin);
in_port_t sin_port; /* Port number. /
struct in_addr sin_addr; / Internet address. */
/* Pad to size of `struct sockaddr’. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};- struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
- struct sockaddr_in
-
struct sockaddr,这个结构体是网络变成接口中用来表示一个IP地址的,注意这个IP地址是不区分IPV4和IPV6的(或者说是兼容两者的)
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
五、IP地址格式转换函数实践
1、inet_addr、inet_ntoa、inet_aton
2、inet_pton、inet_ntop
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDR "192.168.1.102"
// 0x66 01 a8 c0
// 102 1 168 192
// 网络字节序,其实就是大端模式 inet_ntop函数会检测你的电脑是大端还是小端再转换
int main(void)
{
#if 1
char buf[50] = {0};
struct in_addr addr = {0};
addr.s_addr = 0x6601a8c0;
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
printf("ip addr = %s.\n", buf);
#endif
#if 0
int ret = 0;
struct in_addr addr = {0}; //结构体用大括号,虽然结构体里面也是in_addr_t类型
ret = inet_pton(AF_INET, IPADDR, &addr);
if (ret != 1)
{
printf("inet_pton error\n");
return -1;
}
printf("addr = 0x%x.\n", addr.s_addr);
#endif
#if 0
in_addr_t addr = 0;
addr = inet_addr(IPADDR);
printf("addr = 0x%x.\n", addr); // 0x6601a8c0
#endif
return 0;
}
六、soekct实践编程1
1、服务器端程序编写
- socket
- bind(当前电脑的ip和端口号和socket绑定起来)
- listen(监听)
- accept,返回值是一个fd,accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。
- 注意:socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。
- 概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERPORT 6003
#define SERADDR "192.168.5.10" // ifconfig看到的
#define BACKLOG 100
int main(void)
{
int sockfd = -1, ret = -1, clifd = -1;
socklen_t len = 0;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
char ipbuf[30] = {0};
//第1步:先socket打开文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0); //第一个代表IPV4,第二个代表TCP,第三个代表额外的特殊协议
if (-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
// 第2步:bind绑定sockefd和当前电脑的ip地址&端口号
seraddr.sin_family = AF_INET; // 设置地址族为IPv4
seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr)); //防止警告,故强制转换
if (ret < 0)
{
perror("bind");
return -1;
}
printf("bind success.\n");
// 第三步:listen监听端口
ret = listen(sockfd, BACKLOG); // 等待客户端来连接服务器,BACKLOG为100承载能力
if (ret < 0)
{
perror("listen");
return -1;
}
// 第四步:accept阻塞等待客户端接入
clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); //socklen_t len = 0;
printf("连接已经建立,client fd = %d.\n", ret);
return 0;
}
2、客户端程序编写
- socket
- connect
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERADDR "192.168.129.128"
#define SERPORT 6003
int main(void)
{
int sockfd = -1, ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
//第一步socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
//第二步connect连接服务器
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERADDR);
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("listen");
return -1;
}
printf("connect result, ret = %d.\n", ret);
return 0;
}
七、soekct实践编程3
1、客户端发送&服务器接收
- client:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR "192.168.5.10" // 服务器开放给我们的IP地址和端口号
#define SERPORT 9003
char sendbuf[100];
int main(void)
{
// 第1步:先socket打开文件描述符
int sockfd = -1, ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
// 第1步:socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
// 第2步:connect链接服务器
seraddr.sin_family = AF_INET; // 设置地址族为IPv4
seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("listen");
return -1;
}
printf("成功建立连接\n");
#if 0
// 客户端发,服务器收
strcpy(sendbuf, "hello world.");
ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
printf("发送了%d个字符\n", ret);
#endif
#if 0
while (1)
{
printf("请输入要发送的内容\n");
scanf("%s", sendbuf); //这两句相当于上面的strcpy(sendbuf, "hello world.");
ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
printf("发送了%d个字符\n", ret);
}
#endif
#if 1
//客户端收,服务器发
ret = recv(sockfd, sendbuf, sizeof(sendbuf), 0);
printf("成功接收了%d个字节\n", ret);
printf("client发送过来的内容是:%s\n", sendbuf);
#endif
return 0;
}
2、服务器发送&客户端接收
- server:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERPORT 9003
#define SERADDR "192.168.5.10" // ifconfig看到的
#define BACKLOG 100
char recvbuf[100];
int main(void)
{
// 第1步:先socket打开文件描述符
int sockfd = -1, ret = -1, clifd = -1;
socklen_t len = 0;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
char ipbuf[30] = {0};
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
// 第2步:bind绑定sockefd和当前电脑的ip地址&端口号
seraddr.sin_family = AF_INET; // 设置地址族为IPv4
seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind");
return -1;
}
printf("bind success.\n");
// 第三步:listen监听端口
ret = listen(sockfd, BACKLOG); // 阻塞等待客户端来连接服务器
if (ret < 0)
{
perror("listen");
return -1;
}
// 第四步:accept阻塞等待客户端接入
clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
printf("连接已经建立,client fd = %d.\n", clifd);
#if 0
// 建立连接之后就可以通信了
// 客户端给服务器发
ret = recv(clifd, recvbuf, sizeof(recvbuf), 0);
printf("成功接收了%d个字节\n", ret);
printf("client发送过来的内容是:%s\n", recvbuf);
#endif
#if 0
// 客户端反复给服务器发
while (1)
{
ret = recv(clifd, recvbuf, sizeof(recvbuf), 0);
printf("成功接收了%d个字节\n", ret);
printf("client发送过来的内容是:%s\n", recvbuf);
memset(recvbuf, 0, sizeof(recvbuf)); //注意清理
}
#endif
#if 1
// 服务器给客户端发
strcpy(recvbuf, "hello world.");
ret = send(clifd, recvbuf, strlen(recvbuf), 0);
printf("发送了%d个字符\n", ret);
#endif
return 0;
}
3、如何让服务器和客户端好好沟通
- 客户端和服务器原则上都可以任意的发和收,但是实际上双方必须配合:client发的时候server就收,而server发的时候client就收
- 必须了解到的一点:client和server之间的通信是异步的,这就是问题的根源
- 解决方案:依靠应用层协议来解决。说白了就是我们server和client事先做好一系列的通信约定。
八、soekct实践编程4
client:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR "192.168.5.10" // 服务器开放给我们的IP地址和端口号
#define SERPORT 9003
char sendbuf[100];
char recvbuf[100];
#define CMD_REGISTER 1001 // 注册学生信息
#define CMD_CHECK 1002 // 检验学生信息
#define CMD_GETINFO 1003 // 获取学生信息
#define STAT_OK 30 // 回复ok
#define STAT_ERR 31 // 回复出错了
typedef struct commu
{
char name[20]; // 学生姓名
int age; // 学生年龄
int cmd; // 命令码
int stat; // 状态信息,用来回复
}info;
int main(void)
{
// 第1步:先socket打开文件描述符
int sockfd = -1, ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
// 第1步:socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
// 第2步:connect链接服务器
seraddr.sin_family = AF_INET; // 设置地址族为IPv4
seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("listen");
return -1;
}
printf("成功建立连接\n");
#if 0
while (1)
{
// 回合中第1步:客户端给服务器发送信息
printf("请输入要发送的内容\n");
scanf("%s", sendbuf);
ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
printf("发送了%d个字符\n", ret);
// 回合中第2步:客户端接收服务器的回复
memset(recvbuf, 0, sizeof(recvbuf));
ret = recv(sockfd, recvbuf, sizeof(recvbuf), 0);
printf("client发送过来的内容是:%s\n", recvbuf);
// 回合中第3步:客户端解析服务器的回复,再做下一步定夺
}
#endif
#if 1
while (1)
{
// 回合中第1步:客户端给服务器发送信息
info st1;
printf("请输入学生姓名\n");
scanf("%s", st1.name);
printf("请输入学生年龄\n");
scanf("%d", &st1.age);
st1.cmd = CMD_REGISTER;
ret = send(sockfd, &st1, sizeof(info), 0);
printf("发送了1个学生信息\n");
// 回合中第2步:客户端接收服务器的回复
memset(&st1, 0, sizeof(st1));
ret = recv(sockfd, &st1, sizeof(st1), 0);
// 回合中第3步:客户端解析服务器的回复,再做下一步定夺
if (st1.stat == STAT_OK)
{
printf("注册学生信息成功\n");
}
else
{
printf("注册学生信息失败\n");
}
}
#endif
return 0;
}
server:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERPORT 9003
#define SERADDR "192.168.5.10" // ifconfig看到的
#define BACKLOG 100
char recvbuf[100];
#define CMD_REGISTER 1001 // 注册学生信息
#define CMD_CHECK 1002 // 检验学生信息
#define CMD_GETINFO 1003 // 获取学生信息
#define STAT_OK 30 // 回复ok
#define STAT_ERR 31 // 回复出错了
typedef struct commu
{
char name[20]; // 学生姓名
int age; // 学生年龄
int cmd; // 命令码
int stat; // 状态信息,用来回复
}info;
int main(void)
{
// 第1步:先socket打开文件描述符
int sockfd = -1, ret = -1, clifd = -1;
socklen_t len = 0;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
char ipbuf[30] = {0};
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
// 第2步:bind绑定sockefd和当前电脑的ip地址&端口号
seraddr.sin_family = AF_INET; // 设置地址族为IPv4
seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind");
return -1;
}
printf("bind success.\n");
// 第三步:listen监听端口
ret = listen(sockfd, BACKLOG); // 阻塞等待客户端来连接服务器
if (ret < 0)
{
perror("listen");
return -1;
}
// 第四步:accept阻塞等待客户端接入
clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
printf("连接已经建立,client fd = %d.\n", clifd);
//接受数据
while (1)
{
info st;
// 回合中第1步:服务器收
ret = recv(clifd, &st, sizeof(info), 0);
// 回合中第2步:服务器解析客户端数据包,然后干活,
if (st.cmd == CMD_REGISTER)
{
printf("用户要注册学生信息\n");
printf("学生姓名:%s,学生年龄:%d\n", st.name, st.age);
// 在这里服务器要进行真正的注册动作,一般是插入数据库一条信息
// 回合中第3步:回复客户端
st.stat = STAT_OK;
ret = send(clifd, &st, sizeof(info), 0);
}
if (st.cmd == CMD_CHECK)
{
}
if (st.cmd == CMD_GETINFO)
{
}
}
return 0;
}
1、自定义应用层协议第一步:规定发送和接收方法
- 规定连接建立后由客户端主动向服务器发出1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合
- 整个连接的通信就是由N多个回合组成的。
2、自定义应用层协议第二步:定义数据包格式
3、常用应用层协议:http、ftp······
4、UDP简介
——资料来源于朱老师物联网大讲堂