网络编程之基础知识

目录


前言

前面编写的进程间通信:只能用于同一台主机内部不同的进程,而这次讲诉的网络编程也是进程间通信的一种方式,它可以用于不同主机间的进程通信。

一、基础理论

1.  网络分层模型

(1)OSI七层模型

作用:为了方便大家去理解网络通信的整个流程,人为地把计算机网络划分为七个层次。
划分标准:

  • 应用层 :网络服务与最终用户的一个接口,需要用到这个层次中对应的通信协议,http协议(超文本传输协议,开发网页)   ftp协议(文件传输协议)  telnet协议(远程登录)等。
  • 表示层 :数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层)格式有,JPEG、ASCll、EBCDIC、加密格式等。
  • 会话层:建立、管理、终止会话。(在五层模型里面已经合并到了应用层),对应主机进程,指本地主机与远程主机正在进行的会话。
  • 传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层
  • 网络层:找到数据传输的最优路径(路由功能)   协议有:ICMP IGMP IP(IPV4 IPV6)。
  • 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验 等功能。(由底层网络定义协议)。
  • 物理层:建立、维护、断开物理连接。(由底层网络定义协议)。

网络协议:老外给七个层次,每个层次都制定了很多通信协议(网络协议,游戏规则) 

(2)TCP/IP模型

把七层模型简化成了四层,有些资料也会说 TCP/IP 是五层模型,所谓的五层模型指的是在数据链路层下面还有一个物理层,而作为软件工程师一般不需要关注物理层,所以通常我们说 TCP/IP 四层

2. ipv4和ipv6 

  • ipv4地址:32位的地址   文本格式为 nnn.nnn.nnn.nnn,其中 0<=nnn<=255,比如:192.168.24.2   (点分十进制ip,三个小数点划分为4个部分,每个部分各占一个字节)                  
  • ipv6地址:解决ipv4不够用,扩展了位数,扩展到128位,文本格式为 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,其中每个 x 都是十六进制数,表示 4 位。

3. 端口号 

作用:区分同一台主机内部不同的网络进程                

特点:同一台主机内部,端口号不能一样,不同主机,端口号可以是一样的                

本质:无符号短整型数字,取值范围0---65535之间,程序员是可以给程序指定端口号,但是需要注意,1024以内的端口号不要去使用(1024以内的端口号很多被linux系统占用)。

二、使用实例

1.TCP协议

1.tcp协议的通信流程

       

 2、相关API

(1)创建套接字

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

返回值:成功 返回套接字的文件描述符,失败  -1
参数:domain --》地址协议 
                              IPV4地址协议 --》AF_INET或者PF_INET
                              IPV6地址协议 --》AF_INET6或者PF_INET6
           type --》套接字类型   
                         tcp套接字(数据流套接字,流式套接字) --》SOCK_STREAM
                         udp套接字(数据报套接字) --》SOCK_DGRAM
          protocol --》扩展协议,一般默认设置为0    

(2)绑定ip和端口号(自己的ip和端口号)  

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);  //兼容ipv4和ipv6
int bind(int sockfd, const struct sockaddr_in *addr, socklen_t addrlen);  //只能使用IPv4地址
int bind(int sockfd, const struct sockaddr_in6 *addr, socklen_t addrlen); //只能使用IPv6地址

返回值:0时表示绑定成功,-1表示绑定失败
参数: sockfd --》套接字的文件描述符
            addr --》存放你要绑定的ip和端口号
            struct sockaddr     //通用地址结构体,兼容ipv4和ipv6
            {
                    sa_family_t sa_family;  //保存地址协议     2字节
                    char        sa_data[14];  //存放ip和端口号   14字节
             }   16字节 
             struct sockaddr_in  // IPV4地址结构体
             {
                    short  sin_family;  //地址协议
                    struct in_addr  sin_addr;    //存放要绑定的ip地址  4字节
                    short  sin_port;    //存放要绑定的端口号
                    sin_zero; //打酱油的,保证struct sockaddr_in大小跟struct sockaddr一致
              }
              struct  in_addr 
              {
                    in_addr_t  s_addr;  //最终存放要绑定的ip地址
              }
              struct sockaddr_in6  //IPV6地址结构体
              {


               }
addrlen --》地址结构体的大小  sizeof()

        

(3)连接服务器 

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

返回值:如果请求连接成功,则返回0,否则返回错误码

参数:addr --》存放服务器的ip和端口号

(4)监听--tcp协议允许多个客户端连接同一个服务器

 int listen(int sockfd, int backlog);

返回值:成功返回0,失败返回SOCKET_ERROR

参数:backlog(重点) --》最多允许多少个客户端同时连接一台服务器
           如:listen(sockfd,6); 

(5)接受客户端的连接请求,愿意接听    

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:成功返回新的套接字文件描述符,失败  -1
特点:(重点)
           每连接一个客户端,accept都会产生新的套接字
           新的套接字用来跟客户端通信
           accept如果没有客户端连接,会一直阻塞,一旦有客户端连接成功,accept会解除阻塞,返回新的套接字

参数:addr --》存放目前连接成功的客户端的ip和端口号,该变量不需要初始化
           addrlen --》地址长度,要求是指针

(6)读函数

int read(int sockfd, void *buf, size_t count);

int recv(int sockfd, void *buf, size_t len, int flags);详细讲解:请点击

返回值:1、成功,返回读到的字节数

2、如果读取时已经到达文件的末尾或者另一端已关闭则返回0。

3、如果出错,则返回-1

参数--》   sockfd 套接字

                buf 存放数据的指针

                count 想要读取的数据长度

                flags 一组影响此函数行为的标志,一般设为0

(7)写函数

int write(int fd, const void *buf, size_t count);

int send(int sockfd, const void *buf, size_t len, int flags);详细讲解:请点击

返回值:

1.大于0

        (a)等于给定字节数count

        (b)小于给定字节数count,有如下几种可能:     

  • 底层物理介质上没有足够的空间
  • 创建的文件指定了RLIMIT_FSIZE,也就是指定了文件允许的最大字节数,不能再往其中添加数据
  • 已经写了部分数据,但是被中断信号打断,返回中断打断前写入的字节数。

2.返回值=0   

如果相应的errno被设定,说明有相应失败情况。如果errno没有被设定,没有任何影响(可能是write 指定写入0字节等)

3.返回值<0(-1)

出错,查看errno:

  • EAGAIN or EWOULDBLOCK:fd被设定为非阻塞,并且write将会被阻塞,立即返回-1,errno为EAGAIN
  • EINTR:a、阻塞fd:被一个信号打断,但是需要强调的是,在信号打断前没有写入一个字节,才会返回-1,errno设定为EINTR。如果有写入,返回已经写入的字节数。这其实很好理解,如果写入了部分数据依然返回-1,errno设定为EINTR,处理完中断后,由于不知道被打断时写到了什么地方,也就不知道该从哪一个地方继续写入。b、非阻塞fd:调用非阻塞write,即使write被信号打断,write会会继续执行未完成的任务而不会去响应信号。因为在非阻塞调用中,没有任何理由阻止read或者wirte的执行。
  • EPIPE:fd是一个pipe或者socket,而对端的读端关闭。但是一般而言,写进程会收到SIGPIPE信号。(注意:和read不一样,read对端关闭使返回0)
     

(8)关闭文件描述符或套接字

 int close(int fd)  

返回值:关闭成功返回0,失败则返回-1.

2.代码实例

2.1 客户端双向通信代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
/*
	tcp客户端代码--》男朋友
	双向通信
*/
#define RECV_BUF 1024
#define SEND_BUF 1024
int tcpsock;
void *recvservermsg(void *arg)
{
	char otherbuf[RECV_BUF];
	int ret;
	while(1)
	{
		bzero(otherbuf,RECV_BUF);
		ret = read(tcpsock,otherbuf,RECV_BUF);
		if(ret==0)
			break;
		printf("服务器发送过来的信息是:%s\n",otherbuf);
	}
}
int main(int argc,char **argv)
{
	int ret;
	pthread_t id;
	char buf[SEND_BUF];
	
	//定义结构体变量存放对方(服务器)的ip和端口号
	struct sockaddr_in serveraddr;
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);  //服务器的ip地址
	serveraddr.sin_port = htons(atoi(argv[2])); //服务器的端口号

	//创建tcp套接字
	tcpsock = socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)
	{
		perror("创建tcp套接字!\n");
		return -1;
	}
	//设置端口重复使用
	int on = 1;
	setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

	
	//连接服务器
	ret = connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	if(ret==-1)
	{
		perror("连接服务器!\n");
		return -1;
	}
	//创建一个子线程,接收服务器回复的信息
	pthread_create(&id,NULL,recvservermsg,NULL);

	//发送信息给服务器,主线程发送
	while(1)
	{
		bzero(buf,SEND_BUF);
		printf("请输入要发送给服务器的信息!\n");
		scanf("%s",buf);
		write(tcpsock,buf,strlen(buf));
	}
	close(tcpsock);
	return 0;
}

2.2 服务器双向通信代码 

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>

#define RECV_BUF 1024
#define SEND_BUF 1024

int newsock;
void *sendmsgclient(void *arg)
{
	char otherbuf[SEND_BUF];
	while(1)
	{
		bzero(otherbuf,SEND_BUF);
		printf("请输入要发送给客户端的信息!\n");
		scanf("%s",otherbuf);
		write(newsock,otherbuf,strlen(otherbuf));
	}
}

int main(int argc,char **argv)
{
	int tcpsock;
	int ret;
	pthread_t id;
	char buf[RECV_BUF];

	//定义ipv4地址结构体变量
	struct sockaddr_in bindaddr;
	bzero(&bindaddr,sizeof(bindaddr));
	bindaddr.sin_family = AF_INET;
	bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本机任意IP
	bindaddr.sin_port = htons(atoi(argv[1])); //绑定服务器自己的端口号

	struct sockaddr_in clientaddr;
	bzero(&clientaddr,sizeof(clientaddr));
	int addrsize=sizeof(clientaddr);

	//创建tcp套接字
	tcpsock = socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)
	{
		perror("创建tcp套接字!\n");
		return -1;
	}
	printf("旧的文件描述符:%d\n",tcpsock);

	//设置端口重复使用
	int on = 1;
	setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

	//绑定ip和端口号
	ret = bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
	if(ret==-1)
	{
		perror("绑定失败!\n");
		return -1;
	}

	//监听
	ret = listen(tcpsock,8);
	if(ret==-1)
	{
		perror("监听失败!\n");
		return -1;
	}

	//接收客户端的连接请求
	newsock = accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
	if(newsock==-1)
	{
		perror("接收客户端的连接请求失败!\n");
		return -1;
	}
	printf("新的文件描述符:%d\n",newsock);

	//创建一个线程专门发送信息给客户端
	pthread_create(&id,NULL,sendmsgclient,NULL);
	
	//接收信息
	while(1)
	{
		bzero(buf,RECV_BUF);
		ret = read(newsock,buf,RECV_BUF);
		if(ret==0)
			break;
		
		printf("read的返回值是:%d\n",ret);
		printf("客户端发送给我的信息是:%s\n",buf);
	}
}

总结

以上就是今天要讲的内容,本文仅仅简单介绍了网络编程的基础知识和简单的使用案例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值