linux网络编程实践

《朱老师物联网大讲堂》学习笔记

学习地址:www.zhulaoshi.org


(1).
linux网络编程框架,
网络是分层的,OSI是7层的,这种分层是理论的,
实际应用只有4层,TCP/IP,


处理问题时,一定要知道你自己在哪一层,我们目前关注的是应用层,
因为网络是目前最复杂的通信体系,


曾经有很多类似tcp/ip的协议,


CS,client server,客户端服务器架构,
比如qq客户端与qq服务器进行通信,


BS,broswer server,浏览器服务器架构,
可以把浏览器理解为一个通用的客户端,主流的,


(2).
TCP协议传输特性,
工作在传输层,
对上为socket接口提供服务,对下调用ip层,
面向连接,通信前要先进行3次握手建立连接关系,
tcp好比顺丰,传输可靠,


tcp如何保证可靠传输,
接收方ack给发送方,若发送方未收到ack会重传,
校验码,确保数据未损坏,
滑动窗口技术,来调节网络适配速率,
给报文编号,若接收方收到的编号错误,就会重传,


(3).
建立连接的条件;服务器listen时客户端主动发起connect,


TCP的三次握手,双方之间进行3次单向通信,
SYN,客户端connect发送SYN,进入SYN-SEND状态,
SYN+ACK,服务器收到SYN后,服务器端发送SYN-ACK,进入SYN-RCVD状态,
ACK,客户端进入establishd,发送ACK,服务器收到ACK后,服务器进入establisted,
上面这几步骤,没涉及错误的情况,


关闭连接需要4次挥手,
FIN,客户端FIN,
ACK,服务器ACK,
FIN,服务器FIN,
ACK,客户端ACK,
上面是客户端主动关闭,也可以服务器主动关闭,


上面这些握手,挥手都封装在TCP协议内部,和socket没关系,


基于TCP通信的服务模式,
具有公网ip地址的服务器,公网ip地址有限,不是每个人都能有一个,那么可以使用动态公网ip地址映射技术,
服务器端socket,blind,listen后处于监听状态,
客户端端socket后,直接connect去发起连接,
然后双方就可以建立tcp连接收发数据然后关闭连接,


使用tcp协议的应用,
http,ftp,qq服务器,mail服务器,


(4)
socket编程接口,
建立连接,socket,bind,listen/connect,


int socket(int domain, int type, int protocol);
domain,网络域,ipv4或者其它类型,
type,SOCK_STREAM指的是tcp连接,SOCK_DGRAM指的是udp连接,
protocol,给0使用默认协议,
返回值int,返回一个套接字,有点像文件描述符,


int bind( int socket, const struct sockaddr *address, socklen_t address_len);
把本地ip地址和我们的socket绑定起来,
socket是上一步得到的,address不区分ipv4和ipv6,address_len表示结构体的长度,


int listen(int socket, int backlog);
socket第一,二步的那个,
backlog指定同时监听几个,服务器会有个监听队列,


int connect(int socket, const struct sockaddr *address, socklen_t address_len);
address是要连接的服务器的ip地址,socket是之前打开的,


发送和接收,
ssize_t write( int fildes, const void *buf, size_t nbyte);
fildes其实就是socket,


ssize_t send( int socket, const void *buffer, size_t length, int flags );
flag,正常通信用不到,设为0,此时和write差不多,


ssize_t recv(....);
ssize_t read(....);


ip地址十进制形式,点分二进制形式,下面的函数就是负责两种形式的转换,
inet_aton,inet_ntoa,inet_addr,
inet_ntop,inet_pton,
n代表网络net那端使用的二进制形式,p代表字符串也就是255.255.255.255这种形式,
上面3个不支持ipv6,功能差不多,


ip地址相关数据结构都在netinet/in.h,
struct sockaddr,用来表示一个ip地址,兼容ipv6,这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。




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)];
  };




(5)
in_addr_t inet_addr( const char *cp );
把点分十进制格式转换为二进制格式的ip地址,这个函数同时会转换为大端模式,
cp = 192.168.1.102,得到0x6601a8c0,数值正确,顺序不同,也就是大小端!
那怎么办呢?
于是统一规定了一个网络字节序,其实大端模式,

大端可以这样记忆,易于机器读出,因为数据肯定是从低地址开始读(比如0地址),而数字等是从高位开始辨识(千位肯定比各位先读出来),

而小端可以这样记忆,想当然得以为,高位数据放高地址,


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


const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);


(6).
端口号,区分进程,实质是一个数字编号,会包含在每一个发送的数据包中,
bind就是把当前的ip地址和端口号和socket绑定在一起,
int sockfd = 0;
struct sockaddr_in seraddr


sockfd = socket();


htonl,
htons,
h = host,n = net,
l代表4个字节,s代表两个字节


写一部分调试一部分,


(7),(8),(9)

要说的东西主要体现在代码里面,

以下代码为朱老师纯手工打造,

client.c

#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.1.141"		// 服务器开放给我们的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");

/*
	while (1)
	{
		// 回合中第1步:客户端给服务器发送信息
		printf("请输入要发送的内容\n");
		scanf("%s", sendbuf);
		//printf("刚才输入的是:%s\n", 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("成功接收了%d个字节\n", ret);
		printf("client发送过来的内容是:%s\n", recvbuf);

		// 回合中第3步:客户端解析服务器的回复,再做下一步定夺
		
	}
*/

	while (1)
	{
		// 回合中第1步:客户端给服务器发送信息
		info st1;
		printf("请输入学生姓名\n");
		scanf("%s", st1.name);
		printf("请输入学生年龄");
		scanf("%d", &st1.age);
		st1.cmd = CMD_REGISTER;
		//printf("刚才输入的是:%s\n", sendbuf);
		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");
		}

	}



	return 0;
}






server.c

#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.1.141"		// 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;
}


我们要自己去设计安排应用层,数据收发的规则,

http,ftp这些应用层协议,就是这样来的,不过它们设计的很完善。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值