linux网络编程框架
网络是分层的
OSI 7层模型
网络通信是极其复杂的一种通信
TCP/IP协议引入
TCP/IP是用的最多的网络协议实现
TCP/IP分为四层对应OSI的七层
我们编程时最关注应用层,了解传输出,网际互联层和网络接入层不用管
BS和CS
CS架构介绍:(client server 客户端服务器架构)
BS架构介绍:(broswer server 浏览器服务器架构)
TCP协议的学习
理解TCP的重点
TCP协议工作在传输层,对上服务socket接口。对下调用IP层
TCP协议面向连接,通信前必须先三次握手建立连接关系后才能开始通信
TCP协议提供可靠传输,不怕丢包、乱序等
TCP如何保证可靠传输
TCP在传输有效信息前要求通信双方必须先握手,建立连接才能通信
TC在接受方收到数据包后会ack给发送方,若发送方未收到ack会丢包重传
TCP的有效数据内容会附带校验,以防止内容在传递过程中损坏
TCP会根据网络带宽来自动调节适配速率(滑动窗口技术)
发送方会给个 分割报文编号,接收方会校验编号,一旦顺序错误就会重传
TCP的三次握手
建立连接需要三次握手
建立连接的条件:服务器listen时客户端主动发起connect
TCP的四次握手
关闭连接需要四次握手
服务器或者客户端都可以主动发起关闭
基于TCP通信的服务模式
具有公网IP地址的服务器(或者使用动态IP地址映射技术)
服务器端soket、bind、listen后处于监听状态
客户端socket后,直接connect去发起连接
服务器收到同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,双方均可发起,双方均可关闭连接
常见的使用了TCP协议的网络应用
http、ftp
QQ服务器
mail服务器
socket编程接口介绍
建立连接
socket
socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后操作网络连接都通过这个文件描述符
bind(绑定函数)
listen
connect
发送和接收
send和write
recv和read
辅助性函数
inet_aton、inet_addr、inet_ntoa
inet_ntop、inet_pton
表示IP地址相关数据结构
1、都定义在netinet/in.h
2、struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个地址是不区分IPv4和IPv6(兼容)
3、typedef uint32_t inaddr_t;网络内部用来表示IP地址的类型
4、struct in_addr(不常用)
{
in_addr_t s_addr;
}
5、struct sockaddr 是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需这个结构体,此结构体兼容IPv4和IPv6,。在实际编程中此结构体会被一个struct sockaddr_in或者 struct sockaddr_in6 所替代(填充)
6、struct sockaddr_in IPv4
7、struct sockaddr_in6 IPv6
3、in_addr_t、struct in_addr、struct sockaddr_in
IP地址格式转换函数实践
inet_addr、inet_ntoa、inet_aton
inet_addr:不兼容IPv6
#include <stdio.h>
#include <stdlib.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
int main()
{
in_addr_t addr = 0;
addr = inet_addr(IPADDR);
printf("addr = 0x%x.\n",addr);
printf("addr = 0x%x.\n",addr);
return 0;
}
inet_pton、inet_ntop:兼容IPV6使用方式查看man手册
#include <stdio.h>
#include <stdlib.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
int main()
{
int ret = 0;
struct in_addr addr = {0};;
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);
return 0;
}
网络字节序:大端模式
socket实践编程
服务器编写:
socket
bind
listen
htonl(host to net long)将主机字节序转成网络字节序四个字节
htons(host to net short)将主机字节序转成网络字节序两个字节
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define SERADDR "192.168.190.174" ///ifconfig看到的
#define MYPORT 8910
#define BACKLOG 100
int main(void)
{
int sockfd = -1,ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
socklen_t len = 0;
///第一步:先socket打开文件描述符
///AF_INET表示IPv4,SOCK——STREAM表示TCP协议
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket: .\n");
return -1;
}
printf("soketfd = %d.\n", sockfd);
///第二步:bind绑定socket和当前电脑的ip地址和端口号
seraddr.sin_family = AF_INET;///到底是IPv4还是IPv6,设置地址族
seraddr.sin_port = htons(MYPORT);///端口号
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阻塞等待客户端
ret = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
return 0;
}
客户端编写:
socket
connect
概念:端口号,实质是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每个数据包中。 每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR "192.168.190.174" ///ifconfig看到的
#define MYPORT 8910
char sendbuf[100];
int main()
{
int sockfd = -1,ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
///第一步:先socket打开文件描述符
///AF_INET表示IPv4,SOCK——STREAM表示TCP协议
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket: .\n");
return -1;
}
printf("soketfd = %d.\n", sockfd);
///第二步:connect链接服务器
seraddr.sin_family = AF_INET;///到底是IPv4还是IPv6,设置地址族
seraddr.sin_port = htons(MYPORT);///端口号
seraddr.sin_addr.s_addr = inet_addr(SERADDR);///IP地址
ret = connect(sockfd, (const struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("bind: ");
return -1;
}
ret = recv(sockfd, sendbuf, sizeof(sendbuf), 0);
printf("成功接收了%d个字节\n",ret);
printf("client发送过来的内容是: %s\n", sendbuf);
return 0;
}
自定义应用层协议
第一步:规定发送和接收方法
规定连接建立后由客户端主动向服务器发出1个请求数据包,让服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合
整个连接的通信就是由N多个回合组成的。
第二步:定义数据包格式
客户端代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR "192.168.190.174" ///ifconfig看到的
#define MYPORT 8910
#define CMD_REGISTER 1001 ///注册学生信息
#define CMD_CHECK 1002 ///检验学生信息
#define CMD_GETINFO 1003 ///获取学生信息
#define STAT_OK 30 ///回复OK
#define STAT_ERR 31 ///反复错误
char sendbuf[100];
char recvbuf[100];
typedef struct commu
{
char name[20];
int age;
int cmd; //命令码
int stat;
}info;
int main()
{
int sockfd = -1,ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
///第一步:先socket打开文件描述符
///AF_INET表示IPv4,SOCK——STREAM表示TCP协议
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket: .\n");
return -1;
}
printf("soketfd = %d.\n", sockfd);
///第二步:connect链接服务器
seraddr.sin_family = AF_INET;///到底是IPv4还是IPv6,设置地址族
seraddr.sin_port = htons(MYPORT);///端口号
seraddr.sin_addr.s_addr = inet_addr(SERADDR);///IP地址
ret = connect(sockfd, (const struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("connect");
return -1;
}
printf("成功建立连接\n");
/*///建立连接后开始通信
strcpy(sendbuf, "hello world");
ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
printf("发送%d个字节\n", ret);*/
/** while(1)
{
///回合中第一步:客户端给服务器发送信息
printf("请输入要发送的内容\n");
scanf("%s", sendbuf);
ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
printf("发送了%d个字节\n", ret);
///回合中第二步:客户端接收服务器的回复
memset(recvbuf, 0, sizeof(recvbuf));
ret = recv(sockfd, recvbuf, sizeof(recvbuf), 0);
printf("client发送过来的内容是: %s\n", recvbuf);
///回合中第三步:客户端解析服务器的回复,再做下一步
}
**/
while(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("发送了一个学生信息\n");
///回合中第二步:客户端接收服务器的回复
memset(&st1, 0, sizeof(st1));
ret = recv(sockfd, &st1, sizeof(st1), 0);
if(st1.stat == STAT_OK)
{
printf("注册学生信息成功。\n");
}else
{
printf("注册学生信息失败。\n");
}
///回合中第三步:客户端解析服务器的回复,再做下一步
}
ret = recv(sockfd, sendbuf, sizeof(sendbuf), 0);
printf("成功接收了%d个字节\n",ret);
printf("client发送过来的内容是: %s\n", sendbuf);
return 0;
}
服务器代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR "192.168.190.174" ///ifconfig看到的
#define MYPORT 8910
#define BACKLOG 100
#define CMD_REGISTER 1001 ///注册学生信息
#define CMD_CHECK 1002 ///检验学生信息
#define CMD_GETINFO 1003 ///获取学生信息
#define STAT_OK 30 ///回复OK
#define STAT_ERR 31 ///反复错误
char sendbuf[100];
char recvbuf[100];
typedef struct commu
{
char name[20];
int age;
int cmd; //命令码
int stat; //状态信息
}info;
int main(void)
{
int sockfd = -1,ret = -1,clifd = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
char recvbuf[100];
socklen_t len = 0;
///第一步:先socket打开文件描述符
///AF_INET表示IPv4,SOCK——STREAM表示TCP协议
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket: .\n");
return -1;
}
printf("soketfd = %d.\n", sockfd);
///第二步:bind绑定socket和当前电脑的ip地址和端口号
seraddr.sin_family = AF_INET;///到底是IPv4还是IPv6,设置地址族
seraddr.sin_port = htons(MYPORT);///端口号
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);
///客户端反复给服务器发送
while(1)
{
info st;
///回合中第一步:服务器收
ret = recv(clifd, &st, sizeof(info), 0);
///服务器解析客户端数据包
if(st.cmd == CMD_REGISTER)
{
printf("用户注册学生信息");
printf("姓名:%s\n年龄:%d\n", st.name, st.age);
///回合中第三步:回复客户端
st.stat == STAT_OK;
ret = send(clifd, &st, sizeof(info), 0);
}
if(st.cmd == CMD_CHECK)
{
}
if(st.cmd == CMD_GETINFO)
{
}
}
///客户端给服务器发
return 0;
}