1.网络编程基础
1.1引入
socket套接字实现主机之间的通信
cs通信模型基于socket实现,需要客户端软件来实现通信
bs通信模型基于http实现,是网页通信,不需要任何客户端软件
1.2通信协议
(1)OSI七层通信协议:物理层,链路层,网络层,运输层,会话层,表示层,应用层
(2)TCP/IP四层通信协议:网络接口层,网际层,运输层,应用层
(3)五层协议体系: 物理层,数据链路层,网络层,运输层,应用层
分层的好处:各层之间功能独立,每层的功能不需要通过上层或者下层间接得到,上层下层发生变化,不会影响这一层的功能,便于根据每层功能对每层进行查错和维护,促进标准化工作,结构上不可分割。
OSI和TCP/IP每层对比:
数据头:封包和拆包过程:
linux默认封装MTU一帧数据最大为1500字节。
1.3TCP和UDP区别
1.tcp面向有链接传输,有三次握手四次挥手机制,会给每个包编上序列号避免乱序,拥有确认包机制,有超时重传机制,有接收缓存区和发送缓存区,为了提高效率,会将多个小包数据沾成一个包发送,这种现象叫做粘包现象,具有网络拥堵检测,实时性较差,数据传输更稳定,不易造成数据丢失。
2.udp面向无连接传输,没有发送缓存区,有接收缓存区,尽最大努力传输,接收不及时会造成丢包,超过最大发送的数据大小会删掉多出的部分,实时性强,传输效率高,数据容易丢失乱序。
1.4字节序
网络字节序采用大端存储,数据低位存高地址,数据高位存低地址。
ip地址:网络号(三个字节)+主机号(一个字节),共四个字节,如:255.255.255.0
ip地址字节序转换函数:
将主机字节序转为网络字节序: inet_addr(const char*)
将网络字节序转为主机字节序:inet_ntoa(const char*)
端口号:占用两个字节,如:8888
端口号字节序转换函数:
将两字节无符号整数的主机字节序转为网络字节序:htons(uint16_t hostshort)
将两字节无符号整数的网络字节序转为主机字节序:ntohs(uint16_t hostshort)
1.5子网掩码
子网掩码是对主机号的二次划分,子网掩码将原ip划分为多个网段,每个网段绑定一定数量的子网ip地址。子网掩码会将一字节的主机号划分为四字节子网掩码:三字节网络号和一字节主机号,网络号全由1组成,主机号全由0组成。网络号可以向主机号借子网号,借来的子网号的值可以改变,因此可以将原ip划分为多个网段。C类网络默认子网掩码是255.255.255.0
子网地址 = 原ip地址&多个子网掩码(子网掩码个数取决于从主机号借用的可变的子网号的个数)
两个特殊的ip:
主机号全为0:标识网络的起始地址,主机不可用
主机号全为1:全网广播地址,主机不可用
划分后最大的网段个数:2^(子网中1的个数)
划分后最大可用的主机个数:2^(子网中0的个数)-2
1.6端口号
TCP和UDP的端口号是相互独立的
可以使用的:1024~49151,就是我们平时编写服务器使用的端口号
临时端口号:49152~65535,这部分是客户端运行时候动态选择的
1.7域名
一个域名可以绑定多个ip,域名ip可通过shell指令查看:ping www.baidu.com
展示:ubuntu@ubuntu:~/test$ ping www.baidu.com
PING www.a.shifen.com (180.101.50.188) 56(84) bytes of data.
64 字节,来自 180.101.50.188 (180.101.50.188): icmp_seq=1 ttl=128 时间=8.91 毫秒
64 字节,来自 180.101.50.188 (180.101.50.188): icmp_seq=2 ttl=128 时间=13.2 毫秒
ubuntu@ubuntu:~/test$ ping www.baidu.com
PING www.a.shifen.com (180.101.50.242) 56(84) bytes of data.
64 字节,来自 180.101.50.242 (180.101.50.242): icmp_seq=1 ttl=128 时间=22.5 毫秒
64 字节,来自 180.101.50.242 (180.101.50.242): icmp_seq=2 ttl=128 时间=11.0 毫秒
通过上面的结果,可以看到百度官网的域名绑定了多个ip地址
域名结构:
例如域名 http: //www.baidu.com.cn 从右向左看
cn为高级域名,也叫一级域名,它通常分配给主干节点,取值为国家名,cn代表中国
com为网络名,属于二级域名,它通常表示组织或部门
中国互联网二级域名共40个,edu表示教育部门,com表示商业部门,gov表示政府,军队mil
baidu为机构名,在此为三级域名,表示百度
www:万维网world wide web,也叫环球信息网,是一种特殊的信息结构框架。
http:使用的是超文本传输协议
2.tcp和udp通信
1.套接字
套接字socket,通信的载体,使用socket函数创建通信节点,返回该端点的文件描述符,端点中包含发送缓存区和接收缓存区。
2.tcp通信程序
1.服务器端
//实现的功能描述:tcp并发服务器,服务器端可以发送数据给所有连接中的客户端
//实现的功能描述:tcp并发服务器,服务器端可以发送数据给所有连接中的客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
// #define SER_PORT 8888 //服务器端口号
// #define SER_IP "192.168.125.119" //服务器ip地址
int savefd[128] = {};
pthread_mutex_t mutex;
int is_reading = 0;
typedef struct
{
int newfd;
struct sockaddr_in cin;
}info;
void *run(void *arg)
{
struct epoll_event ev;
struct epoll_event events[128];
int flags;
int ret;
int i,j;
int newfd;
newfd=((info *)arg)->newfd;
struct sockaddr_in cin = ((info*)arg)->cin;
//5、与客户端进行相互通信
char rbuf[128] = ""; //读取消息内容的容器
int efd = epoll_create(10);
if(efd < 0)
{
perror("efd is err:");
exit(1);
}
ev.data.fd = newfd;
ev.events = EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, newfd, &ev);
ev.data.fd = 0;
ev.events = EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, 0, &ev);
while(1)
{
//清空容器
bzero(rbuf, sizeof(rbuf));
ret = epoll_wait(efd, events, 128, -1);
if(ret<0)
{
perror("wait error");
}
for(i=0; i<ret; i++)
{
if(events[i].data.fd == 0)
{
//将收到的消息处理一下,回复给客户端
// strcat(rbuf, "*_*");
// 尝试获取锁以读取输入
if (pthread_mutex_trylock(&mutex) == 0) {
// 成功获取锁,设置标志位
is_reading = 1;
scanf("%s", rbuf);
while(getchar()!=10);
// 读取完成,释放锁并清除标志位
is_reading = 0;
pthread_mutex_unlock(&mutex);
}
else{//沒有獲取到鎖就跳過
printf("Thread skipped reading input.\n");
}
//讲消息发送给客户端
//write(newfd, rbuf, strlen(rbuf));
for(j=0;j<128;j++)
{
if(savefd[j] != 0){
send(savefd[j], rbuf, strlen(rbuf), 0);
}
}
printf("发送成功\n");
}
if(events[i].data.fd == newfd)
{
//从套接字中读取数据
//int res = read(newfd, rbuf, sizeof(rbuf));
int res = recv(newfd, rbuf, sizeof(rbuf), 0);
if(res == 0)
{
epoll_ctl(efd, EPOLL_CTL_DEL, newfd, &ev);
// close(newfd);
printf("客户端已经下线\n");
close(newfd);
pthread_exit(NULL);
}
//将读取的消息展示出来
printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf);
}
}
}
close(newfd);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
int pfd;
int i=0;
pthread_t thread;
//1、为通信创建一个端点
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:说明使用的是ipv4通信域
//参数2:说明使用的是TCP面向连接的通信方式
//参数3:由于参数2中已经指定通信方式,填0即可
if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("socket success sfd = %d\n", sfd); //3
//调用端口号快速重用函数
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
//2、绑定ip和端口号
//2.1 准备地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(atoi(argv[2])); //端口号
sin.sin_addr.s_addr = inet_addr(argv[1]); //ip地址
//2.2 绑定工作
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//3、将套接字设置成被动监听状态
if(listen(sfd, 128)==-1)
{
perror("listen error");
return -1;
}
printf("listen success\n");
//4、阻塞等待客户端的连接
//4.1 定义用于接受客户端信息的容器
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1)
{
savefd[i] = accept(sfd, (struct sockaddr*)&cin, &addrlen);
if(savefd[i] == -1)
{
perror("accept error");
return -1;
}
printf("[%s:%d]:发来连接请求\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
info buf;
buf.newfd = savefd[i];
buf.cin = cin;
pfd = pthread_create(&thread, 0, run, &buf);
if(pfd < 0)
{
perror("pthread is err:");
return -1;
}
i++;
pthread_detach(thread);
if (i >= 128) {
perror("Too many connections");
exit(1);
}
}
//6、关闭套接字
close(sfd);
//销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
2.客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
// #define SER_PORT 8888
// #define SER_IP "192.168.125.119"
// #define CLI_PORT 6666
// #define CLI_IP "192.168.250.119"
void handler(int signum)
{
if(signum == SIGCHLD)
{
while(waitpid(-1, 0, WNOHANG) != 0);
}
}
int main(int argc, const char *argv[])
{
//1、创建用于通信的套接字文件描述符
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd == -1)
{
perror("socket error");
return -1;
}
printf("cfd = %d\n", cfd); //3
//2、绑定IP地址和端口号 ?
//2.1 填充客户端地址信息结构体
// struct sockaddr_in cin;
// cin.sin_family = AF_INET;
// cin.sin_port = htons(CLI_PORT);
// cin.sin_addr.s_addr = inet_addr(CLI_IP);
// //2.2 绑定
// if( bind(cfd, (struct sockaddr*)&cin, sizeof(cin)) ==-1)
// {
// perror("bind error");
// return -1;
// }
// printf("bind success\n");
//3、连接服务器
//3.1 准备对端地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
//3.2 连接服务器
if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
{
perror("connect error");
return -1;
}
printf("connect success\n");
//4、数据收发
char wbuf[128] = "";
char rbuf[128] = "";
int ret = fork();
if(signal(SIGCHLD,handler) == SIG_ERR)
{
perror("signal error");
return -1;
}
while(1)
{
if(ret > 0)
{
//从终端上获取要发送的数据
fgets(wbuf, sizeof(wbuf), stdin);
wbuf[strlen(wbuf)-1] = 0;
//将数据发送给服务器
send(cfd, wbuf, strlen(wbuf), 0);
printf("发送成功\n");
}
else
{
//接收服务器发来的消息
bzero(rbuf, sizeof(rbuf));
recv(cfd, rbuf, sizeof(rbuf), 0);
printf("服务器发来的消息为:%s\n", rbuf);
}
}
//5、关闭套接字
close(cfd);
return 0;
}
3.udp通信程序
1.服务器端
2.客户端
3.广播和组播
1.广播
2.组播
4.流式域套接字和报式域套接字
用于本地通信。