笔记--9-17

笔记–9-17

bilibili:《网络编程Tcp/Ip协议》

网络编程

软件结构:
client/server
broswer/server

网络通信协议
协议分类
应用层 HTTP FTP TFTP SMTP SNMP DNS
传输层 TCP UDP
网络层 ICMP IGMP IP ARP RARP
数据链路层 由底层网络定义的协议

UDP用户数据包协议
TCP传输控制协议

网络编程三要素

协议

IP地址 确定计算机
ipv4 32位的二进制数 通常被分为4个字节 a.b.c.d 192.168.1.100 42亿
ipv6 128位 每16个字节一组,分为8组十六进制数 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789

windows下的 cmd 命令
1、ipconfig 查看本机的ip地址
2、ping 192.168.1.100 检查网络是否连通,
3、127.0.0.1 localhost 本机ip地址

端口号 确定软件
逻辑端口
端口号由两个字节组成 取值范围在0-65535之间

TCP通信程序

服务器端先启动,服务器不会主动请求客户端
必须使用客户端请求服务器端,两者之间会建立一个逻辑连接
这个连接会包含一个对象,这个对象就是IO对象
通过IO对象进行通信,通信的数据不仅仅是字符,IO对象是字节流对象

server socket

client socket client socket

客户端和服务器进行一个数据交互,需要4个IO流对象
客-服 outputstream:你好服务器
服-客 inputstream:你好服务器
服-客 outputstream:收到谢谢
客-服 inputstream:收到谢谢

服务器必须明确两件事
1、多个客户端同时和服务器进行交互,服务器必须明确和哪个客户端进行交互
在服务器端有一个方法,叫accept客户端获取到请求的客户端对象 socket s1 = server.accept()socket s2 = server.accept()
2、多个客户端同时和服务器进行交互,就需要使用多个IO 流对象
服务器是没有IO流的,服务器可以获取到请求的客户端对象socket
使用每个客户端socket中提供的IO流和客户端进行交互
服务器使用客户端字节输入流读取客户端发送的数据
服务器使用客户端字节输出流给客户端回写数据
服务器使用客户端的流和客户端交互
弃:讲的是关于java编程的

bilibili:《linux网络编程》

网络字节序:

大端和小端的概念
	大端字节序:也叫高端字节序(网络字节序)低位地址存放高位数据, 高位地址存放低位数据
	小端字节序:也叫高端字节序 							低位地址存放低位数据, 高位地址存放高位数据

大端和小端的使用使用场合???
大端和小端只是对数据类型长度是两个及以上的, 如int short, 对于单字节 没限制, 在网络中经常需要考虑大端和小端的是IP和端口.

思考题: 0x12345678如何存放???

#include <stdio.h>
#include <stdlib.h>

union {
    short s;
    char c[sizeof(short)];
} un2;

union {
	int s;
	char c[sizeof(int)];
}un4;

int main()
{
	printf("[%d][%d][%d]\n", sizeof(short), sizeof(int), sizeof(long int));

	//测试short类型
    un2.s = 0x0102;// 0x0102 =? 16*16+2
    printf("%d,%d,%d\n",un2.c[0],un2.c[1],un2.s);

	//测试int类型
	//un4.s = 0x12345678;
	un4.s = 0x01020304;
	printf("%d,%d,%d,%d,%d\n", un4.c[0], un4.c[1], un4.c[2], un4.c[3], un4.s);
    return 0;
}

执行输出的结果

[2][4][8]
2,1,258
4,3,2,1,16909060

网络传输用的是大端法, 如果机器用的是小端法, 则需要进行大小端的转换.
下面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
上述的几个函数, 如果本来不需要转换函数内部就不会做转换.

** IP地址转换函数:**
p->表示点分十进制的字符串形式
to->到
n->表示network网络 大端模式

**int inet_pton(int af, const char src, void dst);

函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
参数说明:
af: 协议族AF_INET-IPv4 AF_INET6-IPv6
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

例如: IP地址为010aa8c0, 转换为点分十进制的格式:
01---->1 0a---->10 a8---->168 c0---->192
由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1

socket编程用到的重要的结构体

在这里插入图片描述
1、struct sockaddr结构:

struct sockaddr {
        sa_family_t sa_family;
        char     sa_data[14];
   }

2、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--大端模式

通过man 7 ip可以查看相关说明

socket编程主要的API函数介绍

1、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函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列.
在这里插入图片描述
*2、int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen);
函数描述: 将socket文件描述符和IP,PORT绑定
参数说明:
socket: 调用socket函数返回的文件描述符
addr: 本地服务器的IP地址和PORT,
struct sockaddr_in serv; //sockaddr_in结构体
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

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

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

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

*5、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读操作的时候, 若读缓冲区没有数据会引起阻塞.

**使用socket的API函数编写服务端和客户端程序的步骤图示: **
在这里插入图片描述
监听文件描述符lfd
通讯文件描述符cfd

服务端开发流程

1、创建socket,返回一个文件描述符lfd–socket()
–该文件描述符用于监听客户端连接
2、将lfd和IP PORT进行绑定–bind()
3、将lfd由主动变为被动监听–listen()
4、接受一个新的连接,得到一个文件描述符–accept()
–该文件描述符是用于和客户端进行通信的
5、while(1)
{
接收数据–read或者recv
发送数据–write或者send
}
管道是阻塞的
read 普通文件时非阻塞的,读socket、管道文件时阻塞的
6、关闭文件描述符–close(lfd)close(cfd)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值