Linux-C socket网络通信 03.25
socket网络通信 03.25


整体结构类似
依然属于进程间的通信
网络三要素:
IP地址:确定网络中某一台计算机的位置
端口号:确定是哪个应用程序
通信协议:通信时,通信双方预定好的规则
应用程序是静态的,电脑运行起来就是进程
越往上,越偏向软件;越往下,越偏向硬件
数据从应用层开始,每向下一次,会打包一次,直至物理层
TCP:
1、通信时候一定要验证通信双方是否都在线(验证的是,客户端与对应的服务器是否都在线)
2、传输介质:流式IO(byte)
3、理论上,每一次的传输不会限制大小
UDP:
1、不用验证是否在线
2、传输介质:数据报文包
3、每次只能传输64KB
TCP牺牲效率,提高传输的稳定性;UDP牺牲稳定性,提升效率

在大部分使用情况下,TCP连接外网,UDP连接本机;UDP适合在不管对方是否在线的情况下使用
本机IP—网络回环地址:127.0.0.1
ping 127.0.0.1 ping给自己,再自己接收
IPV4:8个字节,

服务器:被动连接
客户端:主动发送请求

SOCKET分类:
•流式套接字(SOCK_STREAM):流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
•数据报套接字(SOCK_DGRAM):数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。
•原始套接字(SOCK_RAW):原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。
sockaddr套接字地址结构:
struct sockaddr
{
unsigned short sa_family; //address族, AF_xxx
char sa_data[14]; //14 bytes的协议地址
};
//sa_family 一般来说, IPV4使用“AF_INET”。
//sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的
sockaddr_in地址结构:
struct sockaddr_in {
short int sin_family; //Internet地址族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //Internet地址
unsigned char sin_zero[8]; //添0(和struct sockaddr一样大小)
};
struct in_addr
{
union{
unsigned long int s_addr;
}
};
注:sockaddr和sockaddr_in两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便
sin_port----字节顺序转换函数:
四个函数的功能:网络字节顺序与本地字节顺序之间相互转换
•htons()——“Host to Network Short” –主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)
• htonl()——“Host to Network Long” –主机字节顺序转换为网络字节顺序(对无符号长型进行操作4bytes)
• ntohs()——“Network to Host Short”–网络字节顺序转换为主机字节顺序(对无符号短型进行操作2bytes)
• ntohl()——“Network to Host Long ”–网络字节顺序转换为主机字节顺序(对无符号长型进行操作4bytes)
之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO。
网络字节顺序NBO(Network Byte Order):
按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题(大端:低位存放在高字节)。
主机字节顺序(HBO,Host Byte Order):
不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关(小端:低位存放在低字节)。
如 Intel x86结构下,short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12 。
如IBM power PC结构下,short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78。
由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power pc那样的顺序 。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。
socket()函数:
功能:为了执行I/O,服务端和客户端必须先调用socket(),产生TCP套接字,作为TCP通信的传输端点;
返回:调用成功,则返回一个较小的非负整数,失败则返回-1;
int socket(int family,int type,int protocol);
//指明协议族
family=AF_INET;//IPv4协议;
family=AF_INET6;//IPv6协议;
family=AF_ROUTE;//路由套接口。
//指明产生套接字的类型
type=SOCK_STREAM;//字节流套接口,TCP常用的形式;
type=SOCK_DGRAM;//数据报套接口,UDP常用的形式 ;
type=SOCK_RAW;//原始套接口
//protocol是协议标志,一般在调用socket()时将其设置为0。(但如果是原始套接字,就需要为其指定一个常值)
bind()函数
功能:为调用socket()函数产生的套接字(socketfd)分配一个本地协议地址,建立地址与套接字(socketfd)的对于关系。
返回:该函数调用成功返回0,若出错则返回-1,并置错误号errno;
int bind(int sockfd,const struct sockaddr *server,socklen_len addrlen);
//sockfd参数是套接字函数返回的套接字描述符;
//server参数是指向特定于协议的地址结构的指针,指定用于通信的本地协议地址;
//addrlen参数是指定该套接字地址结构的长度;
listen()函数
功能:将未连接的套接字转换成被动套接字,使它处在监听模式下,指示内核应接受发向该套接字(socketfd)的连接请求。
返回:调用成功返回0,若出错则返回-1,并置errno值;
int listen(int sockfd,int backlog);
//sockfd参数是要设置的描述符;
//backlog参数规定了请求队列中的最大连接数;
accept()函数
功能:使服务器接受客户端的连接请求。函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被进入睡眠。
返回:调用成功:
(1)accept()函数的返回值,已连接套接字描述符;
(2)addr参数参会客户端的协议地址,包括IP地址和端口号等;
(3)addrlen参数返回客户端地址结构的大小。
如果调用失败则返回-1,并置errno值。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd参数是由sockfd()函数产生的套接字描述符,在调用accept()函数前,已经调用listen()函数将此套接字变成了监听套接字;
//addr参数是用来返回连接对方(客户端)的套接字地址结构;
//addrlen参数是用来返回连接对方的套接字的结构长度
connect()函数
功能:让TCP客户端使用来配置套接字,建立一个与TCP服务器的连接。(激发TCP的三次握手过程)
返回:调用成功后返回0,若出错则返回-1;
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//sockfd参数是有sockd()函数返回的套接字描述符;
//addr参数是指向服务器的套接字地址结构的指针;
//addrlen参数是该套接字地址结构的大小
close()函数
功能:关闭套接字,并立即返回到进程。
int close(int sockfd);
特殊文件描述符号:
•标准输入STDIN_FILENO
•标准输出STDOUT_FILENO
•标准错误STDERR_FILENO
每个进程被加载后,默认打开0,1,2这三个文件描述符
htons()
头文件:#include <arpa/inet.h>
unsigned short int htons(unsigned short int hostshort);
Server
#include <sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
int main()
{
struct sockaddr_in s_addr;
int len = 0;
char buf[50] = { 0 };
int socketfd = 0;
int acceptfd = 0;
int client_num = 0;
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror("socket error");
}
else
{
s_addr.sin_family = AF_INET;//ipv4
s_addr.sin_addr.s_addr = INADDR_ANY;//Internet地址
s_addr.sin_port = htons(10086);
len = sizeof(s_addr);
if (bind(socketfd, (struct sockaddr*)&s_addr, len) == -1)
{
perror("bind error");
}
sleep(1);
if (listen(socketfd, 10) == -1)//监听,判断 请求队列中的最大连接数为10
{
perror("listen error");
}
cout << "ready OK" << endl;//网络通道准备好了
while (1)//死循环,保证服务器长时间运行
{
cout << "wait" << endl;
acceptfd = accept(socketfd, NULL, NULL);//等待客户端上线,阻塞式函数,没有响应,会一直在
//返回客户端的IP地址
cout << "client line succuss --- acceptfd=" << acceptfd << endl;
//网络只能从4开始,0 1 2分别表示操作系统标准输入、输出、报错;3是文件IO操作时候文件描述符号
client_num++;
if (!fork())//开辟子进程进行后续工作
{
while (1)
{
read(acceptfd, buf, sizeof(buf));
printf("Get msg from client%d: %s\n", client_num, buf);
bzero(buf, sizeof(buf));
}
}
}
}
close(socketfd);
return 0;
}
Client
#include <sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
#include<stdio.h>
#include <string.h>
using namespace std;
int main()
{
struct sockaddr_in s_addr;
int len = 0;
int socketfd = 0;
char buf[50] = { 0 };
//
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror("socket error");
}
else
{
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//本机回环ip地址即虚拟机ip 192.168.136.0
s_addr.sin_port = htons(10086);
len = sizeof(s_addr);
//客户端向服务器发送连接请求
if (connect(socketfd, (struct sockaddr*)&s_addr, len) == -1)
{
perror("connect error");
}
else
{
cout << "connect success" << endl;
while (1)
{
cin >> buf;
write(socketfd, buf, sizeof(buf));
}
}
}
close(socketfd);
return 0;
}
桥接与NAT连接:
-
虚拟机网络在桥接连接方式下,虚拟机的ip为主机下的网段,若主机ip地址改变,则虚拟机ip也会改变
-
虚拟机网络在NAT连接方式下,会生成始终固定的局域网段,而虚拟机的ip为网段中的一个ip地址,因此NAT连接相比桥接,有优点是虚拟机的ip不受系统ip地址变化而变化


更多推荐



所有评论(0)