网络编程之没有io多路复用

                 网络编程

www.baidu.com 域名(每个域名都有对应的ip) 202.108.22.5
TCP 有连接的,可靠的,一定可以保证消息可以传达给对方,有三次握手
UDP 没有连接到 不可靠的 不能保证消息一定能够传达给对方,不存在三次握手
利用套接字进行通信:
服务器有两个套接字
一个叫监听套接字
一个叫通信套接字
客户端只有一个套接字
通信套接字
服务器:
bind()绑定ip,端口号(区分进程,电脑里的哪一个进程),域名,网络类型
listen()监听
accept() 建立连接会返回一个通信套接字(用于收发)
recv() 参数:通信套接字
send() 参数:通信套接字
客户端:
建立通信套接字
***ip地址区别网络端 端口号区别服务(进程)
主机是怎么区分不同的网络服务呢?显然不能只靠ip地址,因为ip地址与网络服务的关系是一对多,
实际上是通过:ip地址+端口号来区分不同的网络服务
服务器一般都是通过端口号来识别的。
***点分十进制——————32位的网络字节序
规定就是要用32位的网络字节序
网络类型:ipv4 ipv6
***32位的网络字节序——点分十进制
TCP/IP网络通信协议,是internet最基本的协议,由网络层的ip协议和传输层的tcp协议组成。TCP/IP定义了电子设备如何连入因特网,以及
数据如何在特们之间传输的标准。协议采用了四层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
通俗来说:tcp负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有的数据安全正确的传输到目的地。
而ip是给因特网的每一台电脑规定一个地址。
网络中传输的数据必须按网络字节序,即大端字节序。
小端序:存储数据是先存最低有效位 低序字节存储在低地址,inter amd采用的


虚拟机网络连接:
桥接:桥接到物理机网卡上,虚拟机,物理机,外网都可以进行相互访问
仅主机:虚拟机仅和物理机进行通信
NAT:和桥接相比,外网不能访问虚拟机
TCP/IP协议:传输控制/网际协议 又称作网络通讯协议
TCP/IP协议构架与OSI七层模型:
OSI模型 TCP/IP协议
应用层
表示层
会话层 应用层 WWW,Telnet,FTP
传输层 传输层 TCP/UDP
网络层 网络层 IP,ICMP,IGMP
数据链路层 网络接口与物理层 网卡驱动 物理接口
物理层
传输层:
(1)通信协议
TCP 利用套接字进行通信
TCP协议编程框架:
客户端 服务器
socket(AF_INET,SOCK_STREAM,0) socket(AF_INET,SOCK_STREAM,0)
bind()
listen()
connect() 《 建立连接 》 accept()
send() 请求数据 》 recv() <
recv() 《 回应数据 send() ^
close()
close() 结束连接 》 》 》
传输安全可靠,传输过程中有三次握手,实时性差。
基于连接的通信(可靠通信) 通信前两个进程先握手建立连接,数据不会丢失。
三次握手:
服务器必须准备好接收外来的连接。这通过调用socket(),bind(),listen()函数来完成,称为被动打开。
第一次握手:客户通过调用connect()进行主动打开。这引起客户TCP发送一个SYN(表示同步)分节(SYN=J)
它告诉服务器客户将在连接中发送到数据的初始序列号。并进入SYN_SEND状态,等待服务器的确认。
第二次握手:服务器必须确认客户的SYN,同时自己也要发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。
服务器以单个字节向客户发送SYN和对客户SYN的ACK(表示确认),此时服务器进入SYN_RECV状态
第三次握手:客户收到服务器的SYN+ACK,向服务器发送确认字节ACK+1,此分节发送完毕,客户服务器进入ESTABLISHED状态,完成三次握手。
例如:
主机A,发送序列=200,标志位SYN=1
主机B,发送序列=500,确认序列=201,标志位SYN=1,ACK=1
主机A,发送序列=201,确认序列501,标志位ACK=1
TCP终止连接: 需要四个分节
第一次:某个应用程序首先调用close,我们称这一端执行主动关闭。这一端的TCP于是发送一个FIN字节,表示数据发送完毕。
第二次:接收到FIN的另一端执行被动关闭。这个FIN由TCP确认。它的接收也作为文件结束符传递给接收端应用进程(放在已排队等候应用进程接收到其它数据之后)
第三次:一段时间后,接收到文件结束符的应用进程将调用close关闭它的套接口。这导致它的TCP也发送一个FIN
第四次:接收到这个FIN的原发送端TCP对它进行确认。
例如;
主机A:发送序号=200 确认序号=500 标志位:ACK=1,FIN=1
主机B:发送序号=500 确认序号=201 标志位ACK=1
主机B:发送序号=501 标志位:ACK=1 FIN=1
主机A:发送序列=201 确认序列=502 标志位:ACK=1
IP地址:组成: 网络id:指定了主机所属的网络
主机id:标识了位于该网络的主机
分类:ipv4 包含32位 点分十进制形式(将地址的四个字节写出十进制数字)
ipv6 128
区分:子网掩码 划分:网络id全为1 主机id全为0
表示:192.168.1.0/24 可分配的因特网地址192.168.1.1—192.168.1.254
主机id全为0的用来标识网络本身
主机id全为1标识子网广播地址
255.255.255.0
特殊ip:127.0.0.1 回环地址,分配给主机名localhost
127.0.0.0/8中的所有地址均可分配为回环地址,通常使用127.0.0.0
INADDR_ANY ipv4通配地址 常用于将socket绑定到多宿主机的应用程序
ip转换函数: ipv4
inet_addr
原型:in_addr_t inet_addr(const char*cp);
功能:点分十进制ip转换为网络字节的32位二进制数值并返回
参数:cp 存放输入的点分十进制ip地址的字符串
返回值:成功返回32位的二进制网络字节序地址
inet_ntoa
原型:char *inet_ntoa(struct in_addr in)
功能:32位的网络字节序转换为点分十进制的ip
参数:32位的网络字节序
inet_aton
原型:int inet_aton(const char *cp,struct in_addr *inp)
功能:点分十进制ip转换为32位的网络字节序
参数:cp:存放输入的点分十进制数ip地址字符串
inp:传出参数,保存网络字节序的32位的二进制数值
返回值:成功返回1 失败返回0
例如: inet_aton(“192.168.43.175”,&ser_addr.sin_addr)
自动获取本地ip htonl
如:ser_addr,sin_addr.s_addr=htonl(INADDR_ANY)
INADDR_ANY IPV4通配地址
所属头文件: sys/socket.h netinet/in,h arpa/inet.h
ipv6
原型:int inet_pton(int af,const char *src,void *des) //类似于inet_aton
const char inet_ntop(int af,const void src,char dst,socklen_t cnt)
参数:af: AF_INET :表示ipv4
AF_INET6:表示ipv6
cnt :表示转换之后的字符串长度
端口号:一个是16位无符号整形 0-65535 用来区分一台主机接收到的数据包应该转交给哪个进程来进行处理
1-1023 (1-255之间为众所周知端口,256-1023端口由UNIX系统占用)
如:21/tcp FTP文件传输协议 80/tcp HTTP超文本传输协议(www)
注册端口:1024-49150
动态或私有端口:49151-65535
地址名字转换:
gethostbyname
原型:struct hostent
gethostbyname(const char * hostname)
功能:将域名(www.baidu.com)转换为ip地址
参数:hostname指向存放域名或主机名的字符串
gethostbyaddr
原型:struct hostent
gethostbyaddr(const char
addr,size_t len,int family)
功能:将ip地址转换为域名或主机名
参数:addr是一个ip地址,此时这个ip地址不是普通的字符串,而是要通过函数inet_aton()转换。
len为ip地址的长度,AF_INET为ipv4 AF_INET6为ipv6
头文件:netdb.h
结构体:
struct hostent{
char * h_name; //正式主机名
char * h_aliases ;//主机别名
int h_addrtype; //主机ip地址类型(4 6)
int h_length; //主机ip地址字节长度
char **h_addr_list; //主机的ip地址
}
字节序:
不同类型cpu的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO)
小端序:存储数据是先存最低有效位 低序字节存储在低地址,inter amd等采用的这种方式
大端序:存储数据是先存最高有效位,高序字节存储在低地址 arm motorola等采用
网络中传输的数据必须按网络字节序,即大端字节序。
当应用进程将整数送入SOCKET前,需要转化成网络字节序;当应用进程从socket取出整数后,要转化为主机字节序
转化: 主机》网络 uint16_t htons(uint16_t hostshort)
//将一个无符号短整型的主机数值转化为网络字节序
参数:端口号
网络》主机 uint16_t ntohs(uint16_t netshort);
//将一个16位数由网络字节顺序转换为主机字节顺序
socket系统调用:
socket()系统调用创建一个socket套接字
bind()系统调用将一个socket绑定到一个地址上,服务器需要使用这个调用来将其socket绑定到一个众所周知的
地址上,使得客户端能够定位到该socket上(ip 端口号 网络类型)
listen()系统调用允许一个流socket接收来自其它socket的介接入连接 参数:套接字 监听数量
accept()系统调用在一个监听流socket上接收来自一个对等序列的连接,并可选的返回对等socket地址
connect()系统调用建立与另一个socket之间的连接
套接字编程:
什么是套接字:网络接口函数集,打开的网络,对比文件操作中的文件描述符,socket有描述符,ip地址,端口号
是一套网络编程的接口函数集接口函数集的统称。
分类:流式套接字(SOCK_STREAM) //提高可靠的,面向连接的通信流,保证数据传输的正确性和顺序性
数据报(SOCK_DGRAM) //无连接的服务,独立报文传输,无序,不保证是可靠的,无差错的
原始套接字(SOCK_RAW) //很少用 对底层协议直接访问,用于一些协议开发
重要的数据结构体:
struct sockaddr_in{
short int sin_family; //地址族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //ip地址
unsigned char sin_zero[8]; //未使用
}
struct in_addr{
in_addr_t s_addr; //32位的ipv4地址 网络字节序
}
sa_family AF_INET :ipv4协议
AF_INET6 :ipv6协议
AF_LOCAL :unix域协议
AF_LINK :链路地址协议
AF_KEY :密钥套接字(socket)
服务器操作流程: 头文件:sys/socket.h
(1) 创建监听套接字,用socket函数
api:原型:int socket(int domain,int type,int protocol)
功能:生成一个套接字描述符
参数: domain:网域 AF_INET IPV4 //AF_INET6为ipv6
type: 选择传输协议 tcp/udp
SOCK_STREAM :tcp
SOCK_DGRAM :udp
protocol:基本废弃一般为0
返回值:成功返回描述监听套接字的描述符,失败返回-1
(2) 编写服务器地址信息(填充核心结构体)
包括:使用的地址格式 ip 端口号
这个结构体存的是服务器自己的信息
定义好结构体类型struct sockaddr_in ser_addr;之后,先bzero()
之后进行填充: ser_addr.sin_family=AF_INET; //ipv4
ser_addr.sin_port=htons(PORT); //要将端口号转换为网络字节顺序
ser_addr.sin_addr.s_addr=inet_addr(IP); //要将点分十进制的ip转换为32位的二进制
(3) 将服务器的地址信息与监听套接字绑定,bind()
api: 原型:int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen)
功能:绑定一个端口号和ip地址,使套接口与指定的端口号和ip地址相连接
参数:sockfd :监听套接字 socket()的返回值
my_addr:封装ip地址和端口号 //建立的结构体(注意是否需要强转结构体类型)
addrlen: sockaddr的结构体的长度,通常是计算sizeof(struct sockadd),注意不是指针
返回值:成功0 失败-1
(4) 开始监听(设置允许的最大连接数),listen函数
api: 原型: int listen(int sockfd,int backlog)
功能:使服务器的这个端口和ip处于监听状态,等待网络中某一客户机的连接请求。如果客户端
有连接请求,端口就会接收这个连接。
参数:sockfd 监听套接字 //前面socket()函数的返回值
backlog:指定同时能处理的最大连接要求。5 10 ,最大值可设置128
返回值:成功0 失败-1
(5) 等待来自客户端的连接请求,accept()函数 成功返回通信套接字
api:原型:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
功能:接收远程计算机的连接请求,建立起与客户机之间的通信连接
参数:sockfd :监听套接字 //socket()的返回值
addr :结构体client_addr,用于连接客户端的ip地址和端口号
addrlen:第二个参数的大小,为指针
返回值:成功返回通信套接字描述符,专门用于与建立的客户端通信,失败-1
(6) 收发数据 tcp
发送: 原型:ssize_t send(int s,const void *buf,size_t len,int flags)
功能:用通信套接字发送数据给指定的远端主机
参数: s 通信套接字 //accept()的返回值
buf 要发送的数据缓冲区
len 数据长度
flags:0 表示阻塞
返回值:成功返回真正发送的数据长度,失败-1
接收: 原型:ssize_t recv(int s,const void *buf,size_t len,int flags)
功能:用通信套接字来接收远端主机传来的数据,并把数据存到由参数buf指向的内存空间
参数: s 通信套接字 //accept()的返回值
buf 要发送的数据缓冲区
len 数据长度
flags:0 表示阻塞
返回值:成功返回真正接收的数据长度,失败-1
(7)关闭套接字(网络连接) close(sockfd)
客户端:
1 创建通信套接字,用socket()函数
2 编写服务器地址信息,即设置要连接的服务器的ip地址和端口等属性
3 连接服务器,用connect()函数
api : 原型: int connect(int sockfd,const struct sockaddr *ser_addr,socklen_t addrlen)
功能:用来请求连接远程服务器,将套接口连接到参数ser_addr指定的服务器ip和端口号上去
参数:socket 通信套接字 //socket()的返回值
ser_addr 为结构体指针变量,存储着远程服务器的ip与端口号信息
addrlen 表示结构体变量的长度 注意不是指针
4 收发数据 send和recv或read或write
5 关闭通信套接字(网络连接) close
//sever.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#define PORT 6666
#define IP “127.0.0.1”
int main()
{
int ser_fd = 0,cli_fd = 0;
char buf[1024];
struct sockaddr_in ser_addr,cli_addr;
socklen_t len;
//1.创建监听套接字
ser_fd = socket(AF_INET,SOCK_STREAM,0);
if(ser_fd < 0)
{
perror(“socket”);
return -1;
}
printf(“创建监听套接字成功\n”);
//2.填充核心结构体
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(PORT);
ser_addr.sin_addr.s_addr = inet_addr(IP);
len = sizeof(cli_addr);
//3.bind绑定
if((bind(ser_fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr))) < 0)
{
printf(“绑定套接字失败\n”);
}
printf(“绑定套接字成功\n”);
//4.开始监听
if((listen(ser_fd,10)) < 0)
{
printf(“监听失败\n”);
return -1;
}
printf(“监听成功\n”);
//5.建立连接
cli_fd = accept(ser_fd,(struct sockaddr *)&cli_addr,&len);
if(cli_fd < 0)
{
printf(“连接失败\n”);
return -1;
}
printf(“连接成功\n”);
//6.数据收发
while(1)
{
bzero(buf,sizeof(buf));
recv(cli_fd,buf,sizeof(buf),0);
printf(“来自%s客户端的消息%s\n”,inet_ntoa(cli_addr.sin_addr),buf);
bzero(buf,sizeof(buf));
printf(“请输入你要发送的消息…\n”);
scanf("%s",buf);
send(cli_fd,buf,sizeof(buf),0);
}
//7.关闭套接字
close(ser_fd);
close(cli_fd);
return 0;
}

//client.c
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#define PORT 6666
#define IP “127.0.0.1”
int main()
{
int ser_fd = 0;
char buf[1024];
struct sockaddr_in ser_addr;
//1.创建通信套接字
ser_fd = socket(AF_INET,SOCK_STREAM,0);
if(ser_fd < 0)
{
perror(“socket”);
return -1;
}
printf(“创建通信套接字成功\n”);
//2.填充核心结构体
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(PORT);
ser_addr.sin_addr.s_addr = inet_addr(IP);
//3.建立连接
if((connect(ser_fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr))) < 0)
{
printf(“连接失败\n”);
return -1;
}
printf(“连接成功\n”);
//4.数据收发
while(1)
{
bzero(buf,sizeof(buf));
printf(“请输入你要发送的消息…\n”);
scanf("%s",buf);
send(ser_fd,buf,sizeof(buf),0);
bzero(buf,sizeof(buf));
recv(ser_fd,buf,sizeof(buf),0);
printf(“来自服务器的消息%s\n”,buf);
}
//5.关闭套接字
close(ser_fd);
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值