最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

网络编程打开的第一节预备课

维修 admin 39浏览 0评论

网络编程打开的第一节预备课

一、引言

传统的进程间通信借助内核提供的 IPC 机制进行, 但是只能限于本机通信, 若
要跨机通信, 就必须使用网络通信,比如之前在操作系统学习到的pipe通信,这是一个本机通信,是最基本的IPC机制进行的。

socket网络通信和pipe通信的区别在于:

1. socket可以建立一个socket pair,使用文件描述符操作两个缓冲区,而pipe是两个文件描述符操作一个内核缓冲区。

2. 在网络传输中,需要考虑大端和小端的问题,而pipe通信则不需要。

3. socket通信适用于跨机通信,而pipe通信只能限于本机通信。

 

二、socket 编程预备知识

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

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

比如:0x12345678 如何存放?

将十六进制数 0x12345678 拆分成四个字节,分别为 0x12、0x34、0x56、0x78。

按照大端:0x12345678。

按照小端:0x78563412

如何测试本机是否是大端还是小端

#include <stdio.h>int main() {union {int i;char c[4];} endianTest;endianTest.i = 1;if (endianTest.c[0] == 1) {printf("本机是小端模式\n");} else {printf("本机是大端模式\n");}return 0;
}

网络传输用的是大端法, 如果机器用的是小端法, 则需要进行大小端的转换.

下面 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地址转换函数:

 (1)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字节的整形值.

(2)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

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

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介绍

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

        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函数编写服务端和客户端程序的步骤图示:

测试过程中可以使用netstat命令查看监听状态和连接状态

netstat命令:

        a表示显示所有,

        n表示显示的时候以数字的方式来显示

        p表示显示进程信息(进程名和进程PID)        

常用的netstat -anp | grep 8888  可以查看端口为8888的网络连接状态

四、服务端和客户端代码

客户端代码:

//客户端代码
#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){printf("请输入字符串:\n");//读标准输入数据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("转化为大写的符串为:%s\n",buf);}//关闭套接字cfdclose(cfd);return 0;
}

服务端代码:

//服务端程序
#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(){int lfd = socket(AF_INET,SOCK_STREAM,0);if(lfd < 0){perror("socket error");return -1;}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);int ret = bind(lfd,(struct sockaddr *)&serv,sizeof(serv));if(ret<0){perror("bind error");   return -1;}listen(lfd,128);struct sockaddr_in client;socklen_t len =  sizeof(client);int cfd = accept(lfd,(struct sockaddr*)&client,&len);int i;int n;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);}

按照上面代码,先启动服务端代码,再去启动客户端代码,根据客户端代码提示,完成客户端实现大小写字母转化

网络编程打开的第一节预备课

一、引言

传统的进程间通信借助内核提供的 IPC 机制进行, 但是只能限于本机通信, 若
要跨机通信, 就必须使用网络通信,比如之前在操作系统学习到的pipe通信,这是一个本机通信,是最基本的IPC机制进行的。

socket网络通信和pipe通信的区别在于:

1. socket可以建立一个socket pair,使用文件描述符操作两个缓冲区,而pipe是两个文件描述符操作一个内核缓冲区。

2. 在网络传输中,需要考虑大端和小端的问题,而pipe通信则不需要。

3. socket通信适用于跨机通信,而pipe通信只能限于本机通信。

 

二、socket 编程预备知识

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

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

比如:0x12345678 如何存放?

将十六进制数 0x12345678 拆分成四个字节,分别为 0x12、0x34、0x56、0x78。

按照大端:0x12345678。

按照小端:0x78563412

如何测试本机是否是大端还是小端

#include <stdio.h>int main() {union {int i;char c[4];} endianTest;endianTest.i = 1;if (endianTest.c[0] == 1) {printf("本机是小端模式\n");} else {printf("本机是大端模式\n");}return 0;
}

网络传输用的是大端法, 如果机器用的是小端法, 则需要进行大小端的转换.

下面 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地址转换函数:

 (1)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字节的整形值.

(2)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

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

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介绍

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

        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函数编写服务端和客户端程序的步骤图示:

测试过程中可以使用netstat命令查看监听状态和连接状态

netstat命令:

        a表示显示所有,

        n表示显示的时候以数字的方式来显示

        p表示显示进程信息(进程名和进程PID)        

常用的netstat -anp | grep 8888  可以查看端口为8888的网络连接状态

四、服务端和客户端代码

客户端代码:

//客户端代码
#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){printf("请输入字符串:\n");//读标准输入数据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("转化为大写的符串为:%s\n",buf);}//关闭套接字cfdclose(cfd);return 0;
}

服务端代码:

//服务端程序
#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(){int lfd = socket(AF_INET,SOCK_STREAM,0);if(lfd < 0){perror("socket error");return -1;}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);int ret = bind(lfd,(struct sockaddr *)&serv,sizeof(serv));if(ret<0){perror("bind error");   return -1;}listen(lfd,128);struct sockaddr_in client;socklen_t len =  sizeof(client);int cfd = accept(lfd,(struct sockaddr*)&client,&len);int i;int n;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);}

按照上面代码,先启动服务端代码,再去启动客户端代码,根据客户端代码提示,完成客户端实现大小写字母转化

与本文相关的文章

发布评论

评论列表 (0)

  1. 暂无评论