#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 6799
#define IP "127.0.0.1"
void print_err(char* str){
perror(str);
exit(-1);
}
int main(){
int res_bind=0;
int cfd=0;
struct sockaddr_in serverAddr;
struct sockaddr_in clientAddr;
socklen_t cli_addr_len;
int sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1) print_err("socket fails\n");
//把内存清零,在使用结构体赋值前将缓冲区清零
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(PORT);
//serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);
res_bind=bind(sfd,(struct sockaddr*)&serverAddr,\
sizeof(serverAddr));
if(res_bind==-1) print_err("bind fails\n");
listen(sfd,120);
cli_addr_len=sizeof(clientAddr);
cfd=accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);
if(cfd==-1) print_err("accept fails\n");
/*显示一下哪个客户端连接了*/
char client_IP[100]={0};
printf("client IP=%s,client PORT=%d\n",\
inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\
client_IP,sizeof(client_IP)),\
ntohs(clientAddr.sin_port));
/*转换大小写*/
char buf[30]={0};
int n=read(cfd,buf,sizeof(buf));
int i;
for(i=0;i<n;i++){
buf[i]=toupper(buf[i]);}
write(cfd,buf,sizeof(buf));
close(sfd);
close(cfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 6799
#define IP "127.0.0.1"
void print_err(char* str){
perror(str);
exit(-1);
}
int main(){
int cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1) print_err("socket fails\n");
struct sockaddr_in server_addr;
int connect_res=0;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
connect_res=connect(cfd,(struct sockaddr*)&server_addr,\
sizeof(server_addr));
if(connect_res==-1) print_err("connect fails\n");
/*执行程序*/
//把用户输入读到buf
char buf[100]={0};
fgets(buf,sizeof(buf),stdin);
//把buf内容写到用户端
write(cfd,buf,strlen(buf));
//把返回结果写回屏幕
int n=read(cfd,buf,sizeof(buf));
write(1,buf,sizeof(buf));
close(cfd);
return 0;
}
socket的描述符都指向两个缓冲区
,一个用来读,一个用来写ip地址+端口号
,才能建立网络连接套接字的读写缓冲区在
内核
中定义,所以用户定义的缓冲区,需要借助write和read进行读写用户定义的缓冲区在栈中
在网络通信中,套接字一定是成对出现的
- 一端的发送缓冲区对应另一端的接收缓冲区
其接收端也就是读缓冲区,需要从用户端套接字的写缓冲区读数据
read(cfd,buf,sizeof(buf));
然后本地实现大小写功能后,需要把数据写到用户端
的接收端
write(cfd,buf,sizeof(buf));
首先需要把用户的键盘输入
的内容读到自己定义的用户缓冲区buf
fgets(buf,sizeof(buf),stdin);
然后自己的发送端的内容(客户端写的)写到自己定义的buf,以便输出
write(cfd,buf,strlen(buf));
最后buf的数据,写出到屏幕上
write(1,buf,sizeof(buf));
netstat -apn | grep 具体端口号
#include
#include
#include
#include
#include
#include
#include
void print_err(char* str){
perror(str);
exit(-1);
}
/*socket封装*/
int Socket(int domain, int type, int protocol){
int n=socket(domain,type,protocol);
if(n==-1) print_err("socket fails\n");
return n;
}
/*bind封装*/
int Bind(int sockfd, const struct sockaddr *addr,\
socklen_t addrlen){
int n=bind(sockfd,addr,addrlen);
if(n==-1) print_err("bind fails\n");
return n;
}
/*listen封装*/
int Listen(int sockfd, int backlog){
int n=listen(sockfd,backlog);
if(n==-1) print_err("listen fails\n");
return n;
}
/*accept封装*/
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
int n=accept(sockfd,addr,addrlen);
if(n==-1) print_err("accept fails\n");
return n;
}
/*connect封装*/
int Connect(int sockfd, const struct sockaddr *addr,\
socklen_t addrlen){
int n=connect(sockfd,addr,addrlen);
if(n==-1) print_err("connect fails\n");
return n;
}
#ifndef MY_WRAP
#define MY_WRAP
extern void print_err(char* str);
extern int Socket(int domain, int type, int protocol);
extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern int Listen(int sockfd, int backlog);
extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define PORT 6799
#define IP "127.0.0.1"
int main(){
int cfd,sfd;
char buf[30];
int n;
struct sockaddr_in serverAddr;
struct sockaddr_in clientAddr;
socklen_t cli_addr_len;
//socket()
sfd=Socket(AF_INET,SOCK_STREAM,0);
//bind()
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(PORT);
inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);
Bind(sfd,(struct sockaddr*)&serverAddr,\
sizeof(serverAddr));
//listen()
Listen(sfd,12);
//accept()
cli_addr_len=sizeof(clientAddr);
cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);
/*显示一下哪个客户端连接了*/
char client_IP[100]={0};
printf("client IP=%s,client PORT=%d\n",\
inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\
client_IP,sizeof(client_IP)),\
ntohs(clientAddr.sin_port));
/*转换大小写*/
n=read(cfd,buf,sizeof(buf));
int i;
for(i=0;i<n;i++){
buf[i]=toupper(buf[i]);}
write(cfd,buf,n);
close(sfd);
close(cfd);
return 0;
}
client.c(调用小写函数)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define PORT 6799
#define IP "127.0.0.1"
int main(){
int n;
char buf[100];
int cfd=Socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in server_addr;
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
Connect(cfd,(struct sockaddr*)&server_addr,\
sizeof(server_addr));
//把用户输入读到buf
fgets(buf,sizeof(buf),stdin);
//把buf内容写到用户端
write(cfd,buf,strlen(buf));
//把返回结果写回屏幕
n=read(cfd,buf,sizeof(buf));
write(1,buf,n);
close(cfd);
return 0;
}
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
int n=accept(sockfd,addr,addrlen);
/*异常处理和错误都会返回-1,所以要单独分析
---比如:一个连接被断开了,错误号:ECONNABORTED
---比如:因为收到信号被断开连接,错误号:EINTR
这时候要么重新连接,要么结束连接,这里选“重新连接”
*/
reconnectted:
if(n==-1){
if(errno==ECONNABORTED||(errno==EINTR))
goto reconnectted;
else
print_err("accept fails\n");
}
return n;
}
需要考虑是正常退出(信号主动退出)还是异常退出
正常情况大于0
—实际读到字节数
恰好返回0
----读到了文件末尾,管道被关闭了,socket被关闭
如果没被关闭,还没写数据,不会读到0,因为会阻塞,直到读到或被终止
等于-1
/*read封装*/
ssize_t Read(int fd, void *buf, size_t count){
int n=read(fd,buf,count);
if(n==-1){
again:
if(errno==EINTR||errno==EAGAIN)
goto again;
else
print_err("read fails\n");
}
return n;
}
由于网络层与硬件连接紧密,所以具有不稳定性(路由器宕机,网络传输慢)
传输层:
- 如果
完全不弥补
,尽力而为选用:无连接不可靠的报文传输—UDP
- 如果
完全弥补
选用:面向连接的可靠数据包传输—TCP
请求标志 数据包编号(携带数据大小) 序号
这个序号在网络通讯中用作
临时地址
- 每发一个数据字节,这个序号要加1
- 在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况
- 规定
SYN位
和FIN位
也要占一个序号
- 首先客户端主动发起连接、发送
请求
- 然后服务器端
响应
请求- 然后客户端主动
关闭
连接
miss
表示最大段尺寸
- 如果一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片
- 为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度
在建立连接的同时,双方协商
了一些信息,例如双方发送序号的初始值
、最大段尺寸
等
从序号1001开始的20个字节数据,发送数据
- 确认序号为1021,对序号为1001-1020的数据表示确认收到
- 服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据
对服务器发来的序号为8001-8010的数据表示确认收到
在数据传输过程中,ACK和确认序号
是非常重要的,
发送缓冲区
中收到对方应答的ACK段
才知道该数据包确实发到了对方等待超时后
TCP协议自动将发送缓冲区中的数据包重发如图,不是发送一个数据就会得到一个应答请求
比如发送端发送数据快,接收端可以一下接收三四个数据,再答复ack
只要数据包序号是累加的即可
TCP连接是
全双工
的,因此每个方向都必须单独
进行关闭
一个 FIN只能终止
这个方向
的连接,只意味着这一方向
上没有数据流动
链路层的以太网帧大小很小,目的就是封装的包小,方便丢包后重传
如果数据过大,就只能分割数据,获得多个传输的数据包
//父进程不断通过复制子进程实现多个连接
while(1){
//accept()----别忘了对第三个参数取地址
cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);
//fork()
pid=fork();
if(pid >0){//父进程
close(cfd);//没有请求了,关闭cfd
}
else if(pid==0){
close(sfd);
break;//跳出进入子进程交互
}
}//结束应答
if(pid==0){//子进程去交互
while(1){
//转换大小写
n=Read(cfd,buf,sizeof(buf));
if(n==0){//读到了末尾
close(cfd);
return 0;
}
for(i=0;i<n;i++)
buf[i]=toupper(buf[i]);
write(cfd,buf,n);
}
}
子进程结束,但是父进程没有回收
#include
#include
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
实现:
void wait_child(int singo){
//0---所有子进程无差别回收
while(waitpid(0,NULL,WNOHANG)>0);
exit(-1);
}
if(pid >0){//父进程
close(cfd);//没有请求了,关闭cfd
signal(SIGINT,wait_child);//子进程回收
}
printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\
&serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\
ntohs(serverAddr.sin_port));
inet_ntop
(AF_INET**,&地址.s_addr,写入的缓存地址,**缓存长度);ntohs
(地址.sin_port);#include
#include
#include
#include
#include
#include
#include
#include
void print_err(char* str){
perror(str);
exit(-1);
}
/*socket封装*/
int Socket(int domain, int type, int protocol){
int n=socket(domain,type,protocol);
if(n==-1) print_err("socket fails\n");
return n;
}
/*bind封装*/
int Bind(int sockfd, const struct sockaddr *addr,\
socklen_t addrlen){
int n=bind(sockfd,addr,addrlen);
if(n==-1) print_err("bind fails\n");
return n;
}
/*listen封装*/
int Listen(int sockfd, int backlog){
int n=listen(sockfd,backlog);
if(n==-1) print_err("listen fails\n");
return n;
}
/*accept封装*/
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
int n=accept(sockfd,addr,addrlen);
/*异常处理和错误都会返回-1,所以要单独分析
---比如:一个连接被断开了,错误号:ECONNABORTED
---比如:因为收到信号被断开连接,错误号:EINTR
这时候要么重新连接,要么结束连接,这里选“重新连接”
*/
reconnectted:
if(n==-1){
if(errno==ECONNABORTED||(errno==EINTR))
goto reconnectted;
else
print_err("accept fails\n");
}
return n;
}
/*connect封装*/
int Connect(int sockfd, const struct sockaddr *addr,\
socklen_t addrlen){
int n=connect(sockfd,addr,addrlen);
if(n==-1) print_err("connect fails\n");
return n;
}
/*read封装*/
ssize_t Read(int fd, void *buf, size_t count){
int n=read(fd,buf,count);
if(n==-1){
again:
if(errno==EINTR||errno==EAGAIN)
goto again;
else
print_err("read fails\n");
}
return n;
}
#ifndef MY_WRAP
#define MY_WRAP
extern void print_err(char* str);
extern int Socket(int domain, int type, int protocol);
extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern int Listen(int sockfd, int backlog);
extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern ssize_t Read(int fd, void *buf, size_t count);
#endif
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#include
#include
#define PORT 6767
void wait_child(int singo){
//0---所有子进程无差别回收
while(waitpid(0,NULL,WNOHANG)>0);
exit(-1);
}
int main(){
int sfd,cfd;
struct sockaddr_in serverAddr;
struct sockaddr_in clientAddr;
socklen_t client_addr_len;
pid_t pid;
char buf[100],client_IP[100];
int n,i;
sfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
/*bind()*/
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
//inet_pton(AF_INET,自己的IP,&地址.sin_adds.s_addr)
serverAddr.sin_port=htons(PORT);
Bind(sfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
Listen(sfd,12);
client_addr_len=sizeof(clientAddr);
/*利用多进程实现*/
//父进程不断通过复制子进程实现多个连接
while(1){
//accept()----别忘了取地址
cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);
//打印信息:ip+端口号
printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\
&serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\
ntohs(serverAddr.sin_port));
//fork()
pid=fork();
if(pid >0){//父进程
close(cfd);//没有请求了,关闭cfd
signal(SIGINT,wait_child);//子进程回收
}
else if(pid==0){
close(sfd);
break;//跳出进入子进程交互
}
}
if(pid==0){//子进程去交互
while(1){
n=Read(cfd,buf,sizeof(buf));
if(n==0){//读到了末尾
close(cfd);
return 0;
}
for(i=0;i<n;i++)
buf[i]=toupper(buf[i]);
write(cfd,buf,n);
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define PORT 6767
int main(int argc,char** argv){
int cfd;
char* ip_ADDR=argv[1];//不可以用char ip_ADDR[100]
char buf[100];
struct sockaddr_in serverAddr;
cfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(PORT);
inet_pton(AF_INET,ip_ADDR,&serverAddr.sin_addr.s_addr);
Connect(cfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
/*程序实现*/
while(1){
fgets(buf,sizeof(buf),stdin);
write(cfd,buf,strlen(buf));
int n=read(cfd,buf,sizeof(buf));
write(1,buf,n);}
return 0;
}
#include"wrap .h"的头文件需要放到后面,不然编译不通过
传输层
常见协议有TCP/UDP
协议
- TCP传输控制协议(Transmission Control Protocol)
是一种
面向连接的
、可靠的
、基于字节流的
传输层通信协议
- UDP用户数据报协议(User Datagram Protocol)
是OSI参考模型中一种
无连接的
传输层协议,提供面向事务
的简单不可靠
信息传送服务
应用层
常见的协议有HTTP
协议,FTP
协议
- HTTP超文本传输协议(Hyper Text Transfer Protocol)
- FTP文件传输协议(File Transfer Protocol)
网络层
常见协议有IP协议
、ICMP协议
、IGMP协议
- IP协议是因特网互联协议(Internet Protocol)
- ICMP协议是Internet
控制报文
协议(Internet Control Message Protocol)是TCP/IP协议族的一个子协议,用于在
IP主机、路由器
之间传递控制消息
- IGMP协议是 Internet
组管理
协议(Internet Group Management Protocol)是因特网协议家族中的一个
组播
协议。该协议运行在主机和组播路由器
之间。
网络接口层
常见协议有ARP协议
、RARP协议
、以太网帧协议
- [ARP]协议是正向地址解析协议(Address Resolution Protocol)
通过已知的IP,寻找对应主机的MAC地址
[RARP]是反向地址转换协议
通过MAC地址确定IP地址
浏览器
即可完成数据的传输。C/S模式
B/S模式
客户端
,使用标准浏览器作为客户端,其工作开发量较小缺点:
TCP/IP网络协议栈:
应用层(Application),传输层(Transport),网络层(Network)和链路层(Link)四层
以太网驱动程序
根据以太网首部
中的“上层协议”字段确定该数据帧的有效载荷是IP、ARP还是RARP协议的数据报有效载荷:payload,指除去协议首部之外实际传输的数据
IP数据报
,IP协议再根据IP首部中的“上层协议”字段确定该数据报的有效载荷是TCP、UDP、ICMP还是IGMP端口号
”字段确定应该将应用层数据交给哪个用户进程
IP地址是标识网络中不同主机的地址
而端口号就是同一台主机上标识不同进程的地址
IP地址和端口号合起来标识网络中唯一的进程。
虽然IP、ARP和RARP
数据报都需要以太网驱动程序
来封装成帧
虽然ICMP、IGMP、TCP、UDP
的数据都需要IP协议
来封装成数据报