AndreaDO

导航

网络编程1 基础概念+Socket编程

网络编程1 基础概念+Socket编程

协议

概念: 协议事先约定好, 大家共同遵守的一组规则, 如交通信号灯.
从应用程序的角度看, 协议可理解为数据传输和数据解释的规则;
可以简单的理解为各个主机之间进行通信所使用的共同语言.

分层模型

OSI是Open System Interconnection的缩写, 意为开放式系统互联. 国际标准化组织(ISO)制定了OSI模型, 该模型定义了不同计算机互联的标准, 是设计和描述计算机网络通信的基本框架.
网络分层 OSI 7层模型: 物数网传会表应

  • 物理层---双绞线,光纤(传输介质),将模拟信号转换为数字信号
  • 数据链路层---数据校验,定义了网络传输的基本单位-帧
  • 网络层---定义网络,两台机器之间传输的路径选择点到点的传输
  • 传输层---传输数据 TCP,UDP,端到端的传输
  • 会话层---通过传输层建立数据传输的通道.
  • 表示层---编解码,翻译工作.
  • 应用层---为客户提供各种应用服务,email服务,ftp服务,ssh服务

数据通信过程

通信过程: 其实就是发送端层层打包, 接收方层层解包.
注意: 这些操作不是用户自己做的, 而是底层帮我们做好的.

网络应用程序的设计模式

BS模式 CS模式

  • CS设计模式优缺点:
    优点:
    客户端在本机上可以保证性能, 可以将数据缓存到本地, 提高数据的传输效率, 提高用户体验效果.
    客户端和服务端程序都是由同一个开发团队开发, 协议选择比较灵活.
    缺点:
    服务器和客户端都需要开发,工作量相对较大, 调试困难, 开发周期长;
    从用户的角度看, 需要将客户端安装到用户的主机上, 对用户主机的安 全构成威胁.

  • BS设计模式优缺点:
    优点:
    无需安装客户端, 可以使用标准的浏览器作为客户端;
    只需要开发服务器,工作量相对较小;
    由于采用标准的客户端, 所以移植性好, 不受平台限制.
    相对安全,不用安装软件
    缺点:
    由于没有客户端, 数据缓冲不尽人意, 数据传输有限制, 用户体验较差;
    通信协议选择只能使用HTTP协议,协议选择不够灵活

ARP协议 TCP协议 UDP协议

ARP协议 IP段

UDP数据报格式

TCP数据流格式

在计算机网络中,大端和小端指的是多字节数据类型的存储方式。
数据的低位字节是指数据中数值较小的字节,而内存的高位地址是指内存中地址值较大的地址。
内存地址是指内存中某个存储单元的编号。每个存储单元都具有唯一的地址,地址从 0 开始递增。

在计算机中,内存地址通常由 32 位或 64 位二进制位组成。地址的高位表示内存中哪个存储单元组,低位表示该组内的哪个存储单元。

例如,假设内存地址为 0x12345678,其中:

0x1234 是高位,表示内存中第 0x1234 个存储单元组
5678 是低位,表示该组内的第 5678 个存储单元

**大端(Big Endian)是指高位字节存放到低位地址,低位字节存放到高位地址。

**小端(Little Endian)则是指低位字节存放到低位地址,高位字节存放到高位地址。

下面4个函数就是进行大小端转换的函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
函数名的h表示主机host, n表示网络network, s表示short, l表示long
上述的几个函数, 如果本来不需要转换函数内部就不会做转换.

P地址转换函数:
p->表示点分十进制的字符串形式
to->到
n->表示network网络
**int inet_pton(int af, const char src, void dst);
函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
参数说明:
af: AF_INET
src: 字符串形式的点分十进制的IP地址
dst: 存放转换后的变量的地址
例如: inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

手工也可以计算: 如192.168.232.145, 先将4个正数分别转换为16进制数,
192--->0xC0 168--->0xA8 232--->0xE8 145--->0x91
最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值.

**const char *inet_ntop(int af, const void src, char dst, socklen_t size);
函数说明: 网络IP转换为字符串形式的点分十进制的IP
参数说明:
af: AF_INET
src: 网络的整形的IP地址
dst: 转换后的IP地址,一般为字符串数组
size: dst的长度
返回值:
成功--返回指向dst的指针
失败--返回NULL, 并设置errno

Socket编程

传统的进程间通信借助内核提供的IPC机制进行, 但是只能限于本机通信, 若要跨机通信, 就必须使用网络通信.( 本质上借助内核-内核提供了socket伪文件的机制实现通信----实际上是使用文件描述符), 这就需要用到内核提供给用户的socket API函数库.

socket编程用到的重要的结构体:struct sockaddr

struct sockaddr结构说明:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
struct sockaddr_in结构:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET /
in_port_t sin_port; /
port in network byte order /
struct in_addr sin_addr; /
internet address */
};

** /* Internet address. /*
struct in_addr {
uint32_t s_addr; /* address in network byte order */
}; //网络字节序IP--大端模式

socket编程主要的API函数介绍

**int socket(int domain, int type, int protocol); **
函数描述: 创建socket
参数说明:
domain: 协议版本
AF_INET IPV4
AF_INET6 IPV6
AF_UNIX AF_LOCAL本地套接字使用
type:协议类型
SOCK_STREAM 流式, 默认使用的协议是TCP协议
SOCK_DGRAM 报式, 默认使用的是UDP协议
protocal:
一般填0, 表示使用对应类型的默认协议.
返回值:
成功: 返回一个大于0的文件描述符
失败: 返回-1, 并设置errno

当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列.

*int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen);
函数描述: 将socket文件描述符和IP,PORT绑定
参数说明:

socket: 调用socket函数返回的文件描述符
addr: 本地服务器的IP地址和PORT, 
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);
//INADDR_ANY: 表示使用本机任意有效的可用IP

inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
addrlen: addr变量的占用的内存大小
返回值:
成功: 返回0
失败: 返回-1, 并设置errno

int listen(int sockfd, int backlog);
函数描述: 将套接字由主动态变为被动态
参数说明:
sockfd: 调用socket函数返回的文件描述符
backlog: 同时请求连接的最大个数(还未建立连接)
返回值:
成功: 返回0
失败: 返回-1, 并设置errno

**int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); **
函数说明:获得一个连接, 若当前没有连接则会阻塞等待.
函数参数:
sockfd: 调用socket函数返回的文件描述符
addr: 传出参数, 保存客户端的地址信息
addrlen: 传入传出参数, addr变量所占内存空间大小
返回值:
成功: 返回一个新的文件描述符,用于和客户端通信
失败: 返回-1, 并设置errno值.

accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞.
从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)

*int connect(int sockfd, const struct sockaddr addr, socklen_t addrlen);
函数说明: 连接服务器
函数参数:
sockfd: 调用socket函数返回的文件描述符
addr: 服务端的地址信息
addrlen: addr变量的内存大小
返回值:
成功: 返回0
失败: 返回-1, 并设置errno值

接下来就可以使用write和read函数进行读写操作了.
除了使用read/write函数以外, 还可以使用recv和send函数

读取数据和发送数据:

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);	

对应recv和send这两个函数flags直接填0就可以了.
注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞.

服务端和客户端开发流程

服务端开发流程

  • 1 创建Socket,返回一个文件描述符lfd----socket() 该文件描述符用于监听客户端连接
  • 2 将lfd和IP PORT 进行绑定-----bind()
  • 3 将lfd由主动转被动监听-------listen()
  • 4 接受一个新连接,得到一个新文件描述符cfd, accept()该文件描述符用于和客户端进行通信
  • 5 while(1)
  • 6 关闭文件描述符 ------close(lfd) close(cfd)

服务端代码

//服务端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

int main()
{
	//创建socket
	//int socket(int domain, int type, int protocol);
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}
	
	//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY); //表示使用本地任意可用IP
	int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("bind error");	
		return -1;
	}

	//监听
	//int listen(int sockfd, int backlog);
	listen(lfd, 128);

	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	int cfd = accept(lfd, (struct sockaddr *)&client, &len);  //len是一个输入输出参数
	//const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	
	//获取client端的IP和端口
	char sIP[16];
	memset(sIP, 0x00, sizeof(sIP));
	printf("client-->IP:[%s],PORT:[%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
	printf("lfd==[%d], cfd==[%d]\n", lfd, cfd);

	int i = 0;
	int n = 0;
	char buf[1024];

	while(1)
	{
		//读数据
		memset(buf, 0x00, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or client close, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);	

		for(i=0; i<n; i++)
		{
			buf[i] = toupper(buf[i]);
		}

		//发送数据
		write(cfd, buf, n);
	}

	//关闭监听文件描述符和通信文件描述符
	close(lfd);
	close(cfd);
	
	return 0;
}

编译后运行服务器端,在另外一个端口使用命令:

nc 127.0.0.1 8888

服务器端口

lfd==[3], cfd==[4]

模拟服务端

hello
nihaowodeLinuxhhh

服务器端口

n==[6], buf==[hello
]
n==[18], buf==[nihaowodeLinuxhhh
]

客户端开发流程

  • 1创建socket,返回一个文件描述符cfd ------socket()
  • 2连接服务端 connect()
  • 3while(1)
  • 4close(cfd)

客户端代码

//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main()
{
	//创建socket---用于和服务端进行通信
	int cfd = socket(AF_INET, SOCK_STREAM, 0);
	if(cfd<0)
	{
		perror("socket error");
		return -1;
	}

	//连接服务端
	//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	struct sockaddr_in serv;
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
	printf("[%x]\n", serv.sin_addr.s_addr);
	int ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("connect error");
		return -1;
	}	

	int n = 0;
	char buf[256];
	while(1)
	{
		//读标准输入数据
		memset(buf, 0x00, sizeof(buf));
		n = read(STDIN_FILENO, buf, sizeof(buf));
		
		//发送数据
		write(cfd, buf, n);

		//读服务端发来的数据
		memset(buf, 0x00, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or server closed, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);
	}

	//关闭套接字cfd
	close(cfd);

	return 0;
}

服务器端和客户端验证

一些细节,调用accept函数并不是新建一个连接,而是从已连接队列中取出一个可用连接。

posted on 2024-03-14 18:58  AndreaDO  阅读(9)  评论(0编辑  收藏  举报