linux网络编程——TCP浅析及socket编程实例

1、BS 和 CS

(1)CS架构:(client server,客户端服务器架构)

(2)BS架构:(broswer server,浏览器服务器架构)

 

2、关于TCP理解的重点

(1)TCP协议工作在传输层,对上服务socket接口,对下调用IP层。

(2)TCP协议面向连接,通信前必须先3次握手建立连接关系后才能开始通信。

(3)TCP协议提供可靠传输,不怕丢包、乱序等。

 

3、TCP如何保证可靠传输

(1)TCP在传输有效信息前要求通信双方必须先握手,建立连接才能通信

(2)TCP的接收方收到数据包后会ack给发送方,若发送方未收到ack会丢包重传

(3)TCP的有效数据内容会附带校验,以防止内容在传递过程中损坏

(4)TCP会根据网络带宽来自动调节适应速率(滑动窗口技术)

(5)发送方会给各分割报文编号,接收方会校验编号,一旦顺序错误即会重传。

 

4、TCP的三次握手

(1)建立连接需要三次握手

(2)建立连接的条件:服务器listen时客户端主动发起connect

 

5、TCP的四次握手(挥手)

(1)关闭连接需要四次握手

(2)服务器或者客户端都可以主动发起关闭

注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管。

 

6、基于TCP通信的服务模式

(1)具有公网IP地址的服务器(或者使用动态IP地址映射技术)

(2)服务器端socket、bind、listen后处于监听状态

(3)客户端socket后,直接connect去发起连接

(4)服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,而且双方均可发起。

(5)双方均可发起关闭连接。

 

7、常见的使用了TCP协议的网络应用

(1)http、ftp

(2)QQ服务器

(3)mail服务器

 

8、socket编程接口介绍

8.1、建立连接

(1)socket。

socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。

(2)bind

(3)listen

(4)connect

8.2、发送和接收

(1)send 和 write

(2)recv 和 read

8.3、辅助性函数

(1)inet_aton、 inet_addr、 inet_ntoa

(2)inet_ntop、 inet_pton

8.4、表示IP地址的相关数据结构

(1)都定义在 netinet/in.h

(2)struct socket,

这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)

(3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型

(4)struct in_addr

        {

              in_addr_t s_addr;

         };

(5)struct sockaddr_in

结构体详细信息:

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

};

(6)struct sockaddr

这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼用IPv4和IPv6的。在实际编程中这个结构体会被一个 struct sockaddr_in 或者一个 struct sockaddr_in6所填充。

(7)表示IP地址的相关数据结构

类型小结:in_addr_t、 struct in_addr、 struct sockaddr_in、struct sockaddr

 

9、inet_addr、inet_ntop、 inet_pton函数使用实例:

  • inet_addr                  //IP:点分十进制转二进制
  • inet_ntop                  //IP: 二进制转十进制(字符串显示192.168.xx.xx)
  • inet_pton                  //IP:点分十进制转二进制 (兼容IPv6的新函数)

#include 
#include 
#include 
#include 

#define IPADDR	"192.168.1.102"

//0x66	01	a8	c0
//102	1	168	192
//网络字节序,其实就是大端模式(统一规定的)

int main(void)
{
	struct in_addr addr = {0};
	char buf[50] = {0};
	
	addr.s_addr = 0x6601a8c0;
	
	inet_ntop(AF_INET, &addr, buf, sizeof(buf));	//二进制转十进制(字符串显示192.168.xx.xx)
	
	printf("ip addr = %s.\n", buf);
	
	/* 
	//使用inet_pton来转换
	int ret = 0;
	char buf[50] = {0};
	
	in_addr_t addr = 0;
	
	//addr = inet_addr(IPADDR);	//IP:点分十进制转二进制
	ret = inet_pton( AF_INET, IPADDR, &addr); //IP:点分十进制转二进制 (兼容IPv6的新函数)
	if (ret != 1)
	{
		printf("inet_pton error.\n");
		return -1;
	}
	
	printf("addr = 0x%x.\n", addr);		//0x6601a8c0 */
	
	return 0;
}

 

 

10、socket实践编程

10.1、服务器端程序编写

(1)socket

(2)bind

(3)listen

(4)accept

注意:

accept,返回值是一个fd, accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。

socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;

accept返回的fd叫做连接fd,用来和连接的客户端程序进行读写。

 

10.2、客户端程序编写 

(1)socket

(2)connect

概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往打包在一起不分家的。

 

10.3、

(1)客户端发送 & 服务器接收

(2)服务器发送 & 客户端接收

(3)客户端和服务器原则上都可以任意的发和收,但是实际上双方必须配合:

  • client发的时候server就收,
  • 而server发的时候client就收。

(4)必须了解到一点:client和server之间的通信是异步的。

(5)解决方案:依靠应用层协议来解决。说白了就是我们server和client事先做好一系列的通信约定

 

10.4、自定义应用层协议步骤

(1)自定义应用层协议第一步:规定发送和接收方法

  • 规定连接建立后由客户端主动向服务器发送1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合。
  • 整个连接的通信就是由N多个回合组成的。

(2)、自定义应用层协议第二步:定义数据包格式

 

10.5、socket编程实例

实例功能:
(1)编写服务器、客户端程序,实现服务器/客户端之间的TCP通信
(2)自定义应用层协议,实现学生信息注册、获取等基本功能,                                                                                        如:客户端向服务器提交注册学生信息的请求,服务器收到请求后,注册完成并返回注册成功的信息。
(3)本程序只是走通模拟了这个流程,具体的服务器注册等功能并未实现,可根据具体业务进行拓展。

 

服务器端程序:server.c

//server.c

#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include "pubdata.h"


#define SERPORT 6666	//通信端口号
#define SERADDR "192.168.1.108"		//IP地址  ubuntu中ifconfig看到的
#define BACKLOG 100

char sendbuf[100] = {0};
char recvbuf[100] = {0};


int main(void)
{
	
	int sockfd = -1, ret = -1, clifd = -1;
	socklen_t len = 0;
	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);
	
	//第二步:bind绑定sockfd和当前电脑的ip地址&端口号
	seraddr.sin_family = AF_INET;	//设置地址族为IPv4
	seraddr.sin_port = htons(SERPORT);//htons: host to net short 设置地址的端口号信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	//设置IP地址
	ret = bind(sockfd, ( 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
		 info st;	
		 
		//回合中第一步:服务器收
		ret = recv(clifd, &st, sizeof(info), 0);
		
		//回合中的第二步:服务器解析客户端数据包,然后干活
		if (st.cmd == CMD_REGISTER)
		{
			printf("用户要注册的学生信息\n");
			printf("学生姓名:%s, 学生年龄:%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_REGISTER)
		{
			//具体动作
			
		}

	 }
	 	 
	
	return 0;
}

 

客户端程序:client.c

//client.c

#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 
#include "pubdata.h"


#define SERPORT 6666	            //服务器给客户端开放的通信端口号
#define SERADDR "192.168.1.108"		//服务器给客户端开放的IP地址
#define BACKLOG 100

char sendbuf[100] = {0};
char recvbuf[100] = {0};



int main(void)
{
	
	int sockfd = -1, ret = -1, clifd = -1;
	struct sockaddr_in seraddr = {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;	//设置地址族为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("connect");
		return -1;
	}
	printf("成功建立连接.\n");
	
    //第三步:建立连接之后就可以开始通信了
	while(1)
	{	
        //定义一个用于描述自定义通信协议的结构体类型info,并定义结构体变量st1
		info st1;
		printf("请输入学生姓名\n");
		scanf("%s", st1.name);
		printf("请输入学生年龄\n");
		scanf("%d", &st1.age);
		st1.cmd = CMD_REGISTER;    //使用自定义注册功能码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"); 
		}
			
	}
	
	
	return 0;
}

 

定义数据包格式:

用于描述自定义应用层协议的公用数据结构信息(包含在pubdata.h头文件中):

pubdata.h

//pubdata.h

#ifndef __PUBDATA_H
#define __PUBDATA_H

#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;

#endif

 

上述server.c和client.c程序通过编译链接后,生成两个可执行程序,如:server和client,

首先运行服务器端server,

然后运行客户端client,

正常情况下,双方即成功建立TCP通信,

功能验证:

客户端向服务器端提交学生信息,

服务器收到客户端请求后则会主动回复信息,

如下:

客户端:

linux网络编程——TCP浅析及socket编程实例_第1张图片

服务器端:

 

出现上述结果则表明客户端和服务器端之间通信成功,且定义的数据包格式亦能正常工作,即:

(1)规定连接建立后由客户端主动向服务器发送1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合。

(2)整个连接的通信就是由N多个回合组成的。

 

注:学习资料参考《朱老师物联网大讲堂》——linux网络编程实践

你可能感兴趣的:(Linux,网络编程,嵌入式)