目录
2.UDP(user datagram protocol)协议的特点
1.学习网络编程前的一些基础知识
1.1.IP地址
- IP地址是在IP协议中, 用来标识网络中不同主机的地址(一个IP地址标识一台主机);
- 通常使用 "点分十进制" 的字符串表示IP地址, 例如127.0.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255,就是一个字节的大小,IP地址刚好是4字节, 32位的整数;
1.2.端口号(port)
- 端口号是一个2字节16位的整数
- 端口号用来标识一个进程,那么IP地址+端口号就可以标识某一台主机的某一个进程
1.3.TCP/IP四层模型
1.4.网络字节序
- 网络字节序就是大端字节序(低位放在高地址上),使用相同的字节序便于网络间通信;
有系统提供的接口
2.UDP(user datagram protocol)协议的特点
UDP协议是一个传输层协议
- 无连接:没有连接,客户端发给服务器端,服务器端要先保存客户端的信息,服务器端再使用这个信息发给对应的客户端(简单地说就是需要指明发送给谁)
- 不可靠传输:只是传递数据,成功与否都不会反馈
- 面向数据报:不能向面向字节流的TCP一样使用read和write来读写
3.使用有UPD编写套接字
3.1.服务器端
3.1.1.创建套接字
int sock=socket(AF_INET,SOCK_DGRAM,0);//创建套接字 if(sock<0) { std::cerr<<"socket fail: "<<errno<<std::endl; return 1; }
3.1.2.bind服务器
网络字节序就是大端字节序(低位放在高地址上),使用相同的字节序便于网络间通信;
- 1.需要将人识别的点分十进制,字符串风格IP地址,转化为4字节整数IP.2.考虑大小端的问题
服务器不用bind一个固定的IP的原因
- 1.云服务器,不允许bind公网IP,另外,在一般编写也不会指明IP.2.一般主机有多个IP,如果只是bind一个IP,发给其他IP的数据就不会交给主机处理
- INADDR_ANY只要是发给这个主机的数据都会被处理
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
//1.需要将人识别的点分十进制,字符串风格IP地址,转化为4字节整数IP.2.考虑大小端的问题
//1.云服务器,不允许bind公网IP,另外,在一般编写也不会指明IP.2.一般主机有多个IP,如果只是bind一个IP,发给其他IP的数据就不会交给主机处理;
//INADDR_ANY只要是发给这个主机的数据都会被处理
local.sin_addr.s_addr=INADDR_ANY;
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)//bind主机
{
std::cerr << "bind error : " << errno << std::endl;
return 2;
}
3.1.3.传递接受数据
- UDP是无连接的,需指明发给谁,也需要保存发送端的信息
//业务逻辑
char message[1024];
bool quit=false;
while(!quit){
//保存发送端信息
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
//接受数据
ssize_t s=recvfrom(sock,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);
if(s>0){
message[s]=0;
std::cout<<"client# "<<message<<std::endl;
}else{
std::cerr<<"recvfrom"<<errno<<std::endl;
return 2;
}
//给对端发送一个你好吗
std::string tm="你好吗?";
std::cout<<"server to client: "<<tm<<std::endl;
sendto(sock,tm.c_str(),tm.size(),0,(struct sockaddr*)&peer,len);
}
3.2.客户端
- 客户端不需要显示bind,当传输第一个数据是会自动随机bind一个port(没有被使用的port)
#include<iostream>
#include<cerrno>
#include<string>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
//client serverIP serverPort
int main(int argc,char* argv[])
{
if(argc!=3)
{
std::cout<<"请按格式输入: client serverIP serverPort"<<std::endl;
return 2;
}
int sock=socket(AF_INET,SOCK_DGRAM,0);//创建套接字
if(sock<0)
{
std::cout<<"socket create errno: "<<errno<<std::endl;
return 1;
}
//客户端不用显示bind,OS会自动bind;
//服务器端会有规划,让port是没有被占用的,让别人来访问这个port;
//client正常发送数据的时候,OS会自动给你bind,采用随机端口的方式
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
while(1)
{
std::string message;
std::cout<<"请输入#";
std::cin>>message;
sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
char buffer[1024];
ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&tlen);
if(s>0){
std::cout<<"server say#: "<<buffer<<std::endl;
buffer[s]=0;
}else{
std::cerr<<"recvfrom"<<errno<<std::endl;
return 2;
}
}
return 0;
}
4.使用TCP编写套接字
4.1.TCP的特点
TCP是一个传输层协议,和UDP的特点相反
- 有连接
- 可靠传输
- 面向字节流
4.2.TCP客服端
先写的客户端,因为服务器端会写多个版本
#include<iostream>
#include<stdio.h>
#include<cerrno>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
void Usage()
{
std::cout<<"usage:./client server_IP server_port"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage();
return 1;
}
//建立套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
std::cerr<<"socket"<<errno<<std::endl;
return 2;
}
//自动bind
//连接服务器
struct sockaddr_in local;
local.sin_addr.s_addr=inet_addr(argv[1]);
local.sin_port=htons(atoi(argv[2]));
local.sin_family=AF_INET;
connect(sock,(struct sockaddr*)&local,sizeof(local));
//业务逻辑
while(1)
{
char buffer[1024]={0};
std::cout<<"请输入";
fgets(buffer,sizeof(buffer),stdin);
write(sock,buffer,sizeof(buffer));
char mes[1024]={0};
read(sock,mes,sizeof(mes));
std::cout<<mes<<std::endl;
}
return 0;
}
4.3.TCP服务器端
4.3.1.创建套接字和bind服务器
- socket的第二个参数是SOCK_STREAM,UPD是SOCK_DGRAM(datagram),TCP是SOCK_STREAM(stream,就是字节流)
//建立套接字
int listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
{
std::cerr<<"socket"<<errno<<std::endl;
return 1;
}
//bind服务器
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(PORT);
local.sin_addr.s_addr=INADDR_ANY;//INADDR_ANY会bind执行代码的主机
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind"<<errno<<std::endl;
return 2;
}
4.3.2.listen设为聆听状态
//listen状态
listen(listen_sock,5);
4.3.3.accept接受客户端的连接,并返回一个文件描述符
- 因为TCP套接字是有连接的,连接成功后也是使用返回的套接字和对端进行网络通信
struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
//建立连接
int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
4.4.单进程版本(没有人会使用)
- 一次只能让一个客户端访问
- 写端关闭读端读到文件结尾,再读返回0
int main()
{
//...创建套接字、bind、listen都省略了,每次都一样冗余
while(1)
{
struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
//建立连接
int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
if(fd<0)
{
std::cerr<<"accept "<<errno<<std::endl;
return 3;
}
std::cout<<"get a new link "<<std::endl;
//1.单进程version
while(1)
{
char buffer[1024]={0};
ssize_t s=read(fd,buffer,sizeof(buffer));
if(s>0){
buffer[s]=0;
std::cout<<"client to server:"<<buffer<<std::endl;
std::string message;
message+="server to client:你好!";
//给连接的客户端发一个你好
write(fd,message.c_str(),message.length());
}
else if(s==0){//写端关闭读端读到文件结尾,再读返回0
std::cout<<"client quit!"<<std::endl;
break;
}
else{
std::cerr<<"read "<<errno<<std::endl;
break;
}
}
}
}
4.5.多进程版本
4.5.1.父进程是一个循环,他要一直接收新的客服端不能等待子进程,解决方法
-
父进程等待子进程,子进程创建后再创建孙子进程执行后序代码,子进程秒退等待时间可以忽略不计;下面代码
pid_t pid = fork();
if(pid<0){
continue;
}
else if(pid==0){//子进程
close(listen_sock);
if(fork()>0)
exit(0);//子进程创建就直接退出,创建的孙子进程执行后序代码,孙子进程变成孤儿进程被1号进程领养
serviceIO(fd);
close(fd);
exit(0);
}
else{//父进程
close(fd);//子进程的PCB以父进程的PCB做模板初始化,不是共享的
//waitpid(&pid,NULL,0);//父进程等待子进程,子进程创建后再创建孙子进程执行后序代码,子进程秒退等待时间可以忽略不计
}
2.signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程将自动释放;
2.关闭不用的文件描述符,父进程关闭accept返回的文件描述符,子进程关闭socket返回的文件描述符
void serviceIO(const int& fd)
{
//1.单进程version,做成一个函数
while(1)
{
char buffer[1024]={0};
ssize_t s=read(fd,buffer,sizeof(buffer));
if(s>0){
buffer[s]=0;
std::cout<<"client to server:"<<buffer<<std::endl;
std::string message;
message+="server to client:你好!";
//给连接的客户端发一个你好
write(fd,message.c_str(),message.length());
}
else if(s==0){//写端关闭读端读到文件结尾,再读返回0
std::cout<<"client quit!"<<std::endl;
break;
}
else{
std::cerr<<"read "<<errno<<std::endl;
break;
}
}
}
int main()
{
//...创建套接字、bind、listen都省略了,每次都一样冗余
signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程将自动释放
while(1)
{
struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
//建立连接
int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
std::cout<<"get a new link "<<std::endl;
if(fd<0)
{
std::cerr<<"accept "<<errno<<std::endl;
return 3;
}
//2.多进程version
pid_t pid = fork();
if(pid<0){
continue;
}
else if(pid==0){//子进程
close(listen_sock);
serviceIO(fd);
close(fd);
exit(0);
}
else{//父进程
close(fd);//子进程的PCB以父进程的PCB做模板初始化,不是共享的
}
}
}
4.6.多线程版本
- 线程是共享PCB的,所以在线程内使用完毕关闭文件描述符即可;
- 不等待线程可以使用线程分离的方法
void serviceIO(const int& fd)
{
//1.单进程version
while(1)
{
char buffer[1024]={0};
ssize_t s=read(fd,buffer,sizeof(buffer));
if(s>0){
buffer[s]=0;
std::cout<<"client to server:"<<buffer<<std::endl;
std::string message;
message+="server to client:你好!";
//给连接的客户端发一个你好
write(fd,message.c_str(),message.length());
}
else if(s==0){//写端关闭读端读到文件结尾,再读返回0
std::cout<<"client quit!"<<std::endl;
break;
}
else{
std::cerr<<"read "<<errno<<std::endl;
break;
}
}
}
void* HandlerRequest(void* agrs)
{
pthread_detach(pthread_self());
int fd=*((int*)agrs);
serviceIO(fd);
close(fd);//记得关闭文件描述符
}
int main()
{
while(1)
{
struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
//建立连接
int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
std::cout<<"get a new link "<<std::endl;
if(fd<0)
{
std::cerr<<"accept "<<errno<<std::endl;
return 3;
}
pthread_t tid;
pthread_create(&tid,nullptr,HandlerRequest,(void*)&fd);
}
return 0;
}
5.把套接字封装
- 使用静态是因为不用创建对象就可以使用sock::Socket()等函数;
#pragma once
#include<iostream>
#include<cstdlib>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
namespace ns_socket{
class sock{
public:
static int Socket()
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
std::cerr<<"socket"<<std::endl;
exit(1);
}
return sock;
}
static void Bind(int sock,char* port)
{
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=INADDR_ANY;
local.sin_port=htons(atoi(port));
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind"<<std::endl;
exit(2);
}
}
static void Listen(int sock)
{
if(listen(sock,5)<0)
{
std::cerr<<"listen"<<std::endl;
exit(3);
}
}
static int Accept(int sock)
{
struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
int new_sock=accept(sock,(struct sockaddr*)&tmp,&tlen);
if(new_sock<0)
{
std::cerr<<"accept"<<std::endl;
exit(4);
}
return new_sock;
}
static void Connect(int sock,char* server_ip,char* server_port)
{
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(server_ip);
local.sin_port=htons(atoi(server_port));
if(connect(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"connect"<<std::endl;
exit(5);
}
else
{
std::cout<<"connet success"<<std::endl;
}
}
};
}