当前位置:   article > 正文

【Linux学习笔记】Linux网络编程

linux网络编程

1.linux网络编程框架

网络是分层的

(1)OSI 7层模型

TCP/IP协议引入

(1)TCP/IP协议是用的最多的网络协议实现

应用层调用TCP/IP协议来实现网络通信。

(2)TCP/IP分为4层,对应OSI的7层

应用层,传输层,国际互连层,网络接入层

(3)我们编程时最关注应用层,了解传输层,网际互联层和网络接入层不用管

网络通信的两种主要架构

BS和CS

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

服务器有一个,客户端有多个。客户端通过与服务器联网与服务器通信。网盘,QQ等都是这个模式。

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

浏览器相当于将各家的客户端集成在一起,通过上网找到网页版相当于本机连接上客户端,而该客户端早已和服务器相连接。实现客户端与服务器通信。

2.TCP协议的学习1

关于TCP理解的重点

(1)TCP协议工作在传输层,对上服务socket接口(指的是Linux中与网络编程有关的API),对下调用IP层

应用层调用API进行编程,而API需要调用TCP来工作。

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

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

TCP进行传输是可靠的,TCP协议中的函数不会丢包和乱序(协议指的是诸多代码实现某种功能)

TCP如何保证可靠传输

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

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

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

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

在TCP传输时,一秒发多少个包,一个包多大是自动协商的。具体方式是加入A按一个速度发送数据成功,就慢慢提高速度;假如发送数据成功的变少,就会逐渐减慢速度。TCP会自己去寻找最合适的速度。

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

TCP会给各个数据包打上编号,避免发送时乱序。

3.TCP协议的学习2

TCP的三次握手

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

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

连接状态三次握手:客户端先向服务器发送同步信号,服务器回复SYN+ACK信号,客户端接收后回复ACK信号,并认为已经处于建立连接状态;服务器接收到ACK信号后进入连接状态。

TCP的四次握手

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

客户端先向服务器发送FIN+ACK,服务器回复一个ACK信号。

然后再由服务器向客户端发送FIN,客户端回复ACK信号。

四次握手后断开连接。

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

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

基于TCP通信的服务模式

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

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

Socket用来返回一个socket接口, bind用来与本地绑定, listen用于监听。

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

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

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

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

(1)http应用层协议、ftp  底层使用TCP协议。

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,采用三次握手建立连接,四次挥手终止连接。

HTTP(Hypertext Transfer Protocol,超文本传送协议),是互联网上应用最为广泛的一种网络协议。

TCP是传输层协议,定义数据传输和连接方式的规范

HTTP是应用层协议,定义传输数据的内容的规范

HTTP协议基于TCP协议,所以支持HTTP也就一定支持TCP

(2)QQ服务器

(3)mail服务器

4.socket编程接口介绍

建立连接

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

socket()为通信创建一个端点,并返回一个指向该端点的文件描述符。 调用成功后返回的文件描述符将是当前没有为进程打开的最低编号的文件描述符。

int socket(int domain, int type, int protocol);

 domain参数指定了一个通信域,如AF_INET代表IPV4,AF_INET6代表IPV6

Type    指定套接字的类型,

SOCK_STREAM  提供有序可靠的双向的连接模式的数据流,即传输层使用TCP.

SOCK_DGRAM   传输层使用UDP

SOCK_SEQPACKET

 protocol    指定一个具体的协议,如果是0就是默认协议。

(2)bind   对文件描述符进行属性上的修改类似于fcntl,如果成功则返回值0.

bind()将addr指定的地址分配给文件描述符sockfd所指向的套接字。

       int bind(int sockfd, const struct sockaddr *addr,

                socklen_t addrlen);

Sockfd       由socket函数得到的端点的文件描述符

const struct sockaddr *addr     用来表示一个IP地址,

socklen_t addrlen   结构体的大小

(3)listen 用于监听端口,需要被监听的客户端请求按先后顺序放到监听队列中。

int listen(int sockfd, int backlog);

Sockfd  socket函数返回得到的文件描述符

Backlog 表示监听队列的长度

(4)connect

客户端软件先调用socket创建端点,再connect连接服务器

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

Sockfd   客户端创建端点得到的文件描述符

const struct sockaddr *addr    要链接的服务器的IP地址

socklen_t addrlen      地址结构体的长度

自己的IP地址协议栈会自动放到报文中,不用手动添加。

发送和接收

这里的write和read与文件IO的读写是一样的。

(1)send和write

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

         sockfd 经socket创建得到的文件描述符

         flags  文件传输的类型

如: MSG_EOR  记录传输将要结束

 MSG_OOB  发送带外数据

send函数中的flag只有在一些特殊协议中才会用到,默认为0。

(2)recv和read

recv函数与send函数用法类似

辅助性函数(主要用于IP地址转换,将点分十进制与二进制形式互换)

(1)inet_aton、inet_addr、inet_ntoa(仅支持IPV4)

inet_addr 将点分十进制(以字符串的形式输入)转化为二进制数

inet_ntoa 将32位2进制数转化为点分十进制

(2)inet_ntop、inet_pton(这两个支持IPV6)

inet_ntop 32位转化为点分十进制形式,inet_pton点分十进制转化为32位

表示IP地址相关数据结构

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

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

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

(4)struct in_addr                                            //表示IP地址的类型

  {

    in_addr_t s_addr;

  };

(5)struct sockaddr_in                                   //封装IPV4的IP地址

  {

    __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所填充。

5.IP地址格式转换函数实践

inet_addr、inet_ntoa、inet_aton

inet_pton、inet_ntop

#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 = 0x6703a8c0;

inet_ntop(AF_INET, &addr, buf, sizeof(buf));

printf("ip addr = %s.\n", buf);

/*        

// 使用inet_pton来转换

int ret = 0;

struct in_addr addr = {0};

ret = inet_pton(AF_INET, IPADDR, &addr);

//AF_INET 网络的域,此参数表示IPV4。

if (ret != 1)

{

printf("inet_pton error\n");

return -1;

}

printf("addr = 0x%x.\n", addr.s_addr);

*/        

/*

in_addr_t addr = 0;

addr = inet_addr(IPADDR);               //将点分十进制IP地址转化为二进制数,这一类函数可以将IP地址统一为大端模式。

printf("addr = 0x%x.\n", addr);                // 0x6601a8c0

*/        

return 0;

}

6.socket实践编程1_2

服务器端程序编写

(1)socket   创建一个网络连接的节点

(2)bind  相当于将当前要上网的进程的的IP地址和端口号和socket连接起来

(3)listen

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

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);   //该函数为阻塞式,等待客户端函数接入期间阻塞。

addrlen参数:是一个输出型参数,调用者必须对其进行初始化;返回时,它将包含对等地址的实际大小。

 struct sockaddr *addr 这也是一个输出型参数,可以获取访问的客户端的IP地址。

注意:socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写

#define SERPORT                9003

#define SERADDR                "192.168.129.128"                // ifconfig看到的

#define BACKLOG                100

端口号是自己选定的,端口号范围在0-65535.

大多数TCP/IP实现给临时端口号分配1024---5000之间的端口号。大于5000的端口号是为其他服务器预留的

如果要自己选端口号从5000以上挑选即可。

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地址,并将点分十进制转化为32位2进制。

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

//参数addr和addrlen用来返回已连接的对端进程(客户端)的协议地址,这是两个输出型参数

printf("连接已经建立,client fd = %d.\n", ret);

return 0;

}

客户端程序编写

(1)socket

(2)connect

define SERADDR                "192.168.129.128"                // 服务器开放给我们的IP地址和端口号

#define SERPORT                9003

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("connect result, ret = %d.\n", ret);

return 0;

}

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

Makefile:

all:

gcc server.c -o ser

gcc client.c -o cli

clean:

rm ser cli *.o

socket实践编程3

客户端发送&接收

/*        

// 建立连接之后就可以开始通信了

strcpy(sendbuf, "hello world.");

ret = send(sockfd, sendbuf, strlen(sendbuf), 0);

printf("发送了%d个字符\n", ret);

*/

/*

while (1)

{

printf("请输入要发送的内容\n");

scanf("%s", sendbuf);

//printf("刚才输入的是:%s\n", sendbuf);

ret = send(sockfd, sendbuf, strlen(sendbuf), 0);

printf("发送了%d个字符\n", ret);

}

*/

//客户端接收

ret = recv(sockfd, sendbuf, sizeof(sendbuf), 0);

printf("成功接收了%d个字节\n", ret);

printf("client发送过来的内容是:%s\n", sendbuf);

服务器发送&接收

/*        

// 建立连接之后就可以通信了

// 客户端给服务器发

ret = recv(clifd, recvbuf, sizeof(recvbuf), 0);

printf("成功接收了%d个字节\n", ret);

printf("client发送过来的内容是:%s\n", recvbuf);

*/

/*

// 客户端反复给服务器发

while (1)

{

ret = recv(clifd, recvbuf, sizeof(recvbuf), 0);

//printf("成功接收了%d个字节\n", ret);

printf("client发送过来的内容是:%s\n", recvbuf);        

memset(recvbuf, 0, sizeof(recvbuf));

}

*/

// 服务器给客户端发

strcpy(recvbuf, "hello world.");

ret = send(clifd, recvbuf, strlen(recvbuf), 0);

printf("发送了%d个字符\n", ret);

探讨:如何让服务器和客户端好好沟通

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

(2)必须了解到的一点:client和server之间的通信是异步的,这就是问题的根源

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

7.socket编程实践4

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

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

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

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

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

客户端

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

}

}

服务器

// 客户端反复给服务器发

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)

{

}

常用应用层协议:http、ftp······

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/631706
推荐阅读
相关标签
  

闽ICP备14008679号