作用:在网络中唯一一台主机
类型:
在一台主机上标识一个进程(发往哪个端口的数据应该由哪个进程来处理)
理解源端口号和目的端口号唐僧例子、送快递例子
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”
字节序:CPU在内存中对数据进行存取的顺序
主机字节序:0x 01 02 03 04
大端字节序:低地址存高位 uint_8 a[4] a[0]=01, a[1]=02, a[2]=03, a[3]=04
小端字节序:低地址存地位 uint_8 a[4] a[0]=04, a[1]=03, a[2]=02, a[3]=01
主机字节序决定于CPU架构:intel的CPU架构X86(小端),MIPS(大端)
不同主机字节序的主机传输数据(大于一个字节)会存在二义性(数据存取是按字节处理—因此字节序针对的是大于一个字节存储的类型)
short int long float double
网络数据传输中,有可能因为通信双方主机字节序不同而造成数据二义
因此规定在网络通信中使用大端字节序作为网络字节序成为通信的字节序标准
如何判断主机字节序? union联合体
union
{
char a;如果是01就是小端,如果是00就是大端
int b;b存00 00 00 01;
}u
u.b=00000001
if(u.a==01)
{//小端0}
if(u.a==00)
{//大端}
或类型强转:int a=1 uchar*b=(uchar*)&a
TCP协议:
UDP协议:
TCP因为为了实现可靠传输,因此牺牲了部分性能,适用于安全性要求高的传输场景:文件传输
UDP因为你不用实现可靠传输,因此传输速度块,适用于实时性要求高的传输场景:视频传输
通过套接字使进程与网卡建立联系:内核中创建socket结构体
#include /* See NOTES */
#include
int socket(int domain, int type, int protocol);
domain: 地址域 AF_INET—使用IPV4网络协议地址域
type: 套接字类型
protocol: 传输层协议 协议声明in.h
0 根据套接字类型使用默认协议
TCP协议:
IPPROTO_TCP = 6, /* Transmission Control Protocol. */
#define IPPROTO_TCP IPPROTO_TCP
UDP协议:
IPPROTO_UDP = 17, /* User Datagram Protocol. */
#define IPPROTO_UDP IPPROTO_UDP
返回值:
RETURN VALUE:
On success, a file descriptor for the new socket is returned.
On error, -1 is returned, and errno is set appropriately.
#include /* See NOTES */
#include
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd: 创建套接字返回的套接字描述符
addr: 地址信息
addrlen: 地址信息长度
返回值:
#include
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
==使用recvfrom==
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
**sockfd:**套接字描述符
buf: 用于接收数据的缓冲区
len: 想要接收的数据长度(主要作用是为了限制buf防止越界)
flags: 选项标志
src_addr: 发送端地址信息
addrlen: 地址信息长度(输入输出型参数:指定地址想要的长度,返回实际的长度)
返回值:
#include
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
==使用sendto==
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
sockfd: 套接字描述符
buf: 要发送的数据
len: 要发送的数据长度
flags:
dest_addr: 目的端地址
addrlen: 地址长度
返回值:
#include
int close(int fd);
fd: 套接字描述符
返回值:
按地址解析:
通过地址域判断是哪一个地址域,再按照指定的地址域去解析剩下的数据
端口解析:
因为端口和地址都是要在网络上传输的,因此需要字节序转换
#include
uint32_t htonl(uint32_t hostlong);
//将32位的数据从主机字节序转换为网络字节序
uint16_t htons(uint16_t hostshort);
//将16位的数据从主机字节序转换为网络字
uint32_t ntohl(uint32_t netlong);
//将32位的数据从网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort);
//将16位的数据从网络字节序转换为主机字节序
#include
#include
#include
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
//将字符串点分十进制IP地址转换为网络字节序IP地址
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
//通过网络字节序IP地址转换为字符串点分十进制IP地址
struct in_addr inet_makeaddr(int net, int host);
n_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
inet_aton(), inet_ntoa(): _BSD_SOURCE || _SVID_SOURCE
UDP模拟实现
tcp: 面向链接,可靠传输,面向字节流
1. 创建套接字:
socket(AF_INT,SOCK_STREAM,IPPROTO_TCP)
2. 为套接字绑定地址信息
bind(sockfd,struct sockaddr_in *addr,addrlen)
3. 开始监听
listen(sockfd,backlog)
#include /* See NOTES */
#include
int listen(int sockfd, int backlog);
backlog:
4. 获取完成连接的socket
#include /* See NOTES */
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include
int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);
sockfd: 套接字描述符
addr: 新建连接的客户端地址信息
addrlen: 新建客户端的地址信息长度
返回值: 返回新建客户端socket的描述符
int newsockfd=accept(sockfd,*client_addr,*addrlen)
新的sockfd仅仅用于与指定客户端进行单独通信
向服务端发送请求
#include /* See NOTES */
#include
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd: 套接字描述符
addr: 服务端地址信息
addrlen: 地址信息长度
5. 接受数据
recv(newsockfd,buf,len,flag)
6. 发送数据
send(newsockfd,data,len,flag)
while(1){
获取已完成连接的新客户端:socket clientfd=accept(sockfd,cliaddr,addrlen)
接收数据:revc(clientfd,buf,buflen,flag)
返回值:
>0实际接收字节
==0连接断开
<0出错
发送数据:send(clientfd,data,datalen,flag)
}
7. 关闭连接
close(newsockfd)
close(sockfd)
1. 创建套接字
2. 绑定地址信息
不推荐手动绑定固定地址
3. 向服务端发起连接请求
connect(sockfd,srvaddr,addrlen)
4. 发送数据
5. 接受数据
6. 关闭socket
TCP模拟实现
tcp自己实现了报货机制:当长时间没有数据通信,服务端会想客户端发送保活探测包;当这些保活探测包连续多次都没有响应,则认为连接断开
recv返回0;send触发异常
最基本的tcp服务端程序同一时间只能与一个客户端通信一次
因为用户不知道客户端什么时候有数据到来/不知道什么时候有新的客户端连接请求到来;所以程序只能按照固定套路流程运行;这时候程序的流程就有可能因为accept/recv函数的阻塞特性导致程序阻塞,导致服务端无法同时处理多个客户端请求
解决方法:
多线程或多进程
使用多进程tcp服务端程序的处理多客户端请求;每当一个客户端的连接到来,都创建一个新的子进程,让子进程单独与客户端进行通信;这样的话父进程永远只处理新连接
当获取到已完成连接的客户端socket,则为这个新的socket创建一个进程/线程来单独为这个客户端服务(通信)
对主进程/父进程来说要做的事情就一件:处理连接,获取连接
多进程/多线程能够解决服务器同时处理多个客户端数据的本质:是每个线程/进程都是一个独立的执行流
#include "tcpsocket.hpp"
#include
#include
void sigcb(int no)
{
while(waitpid(-1,NULL,WNOHANG)>0);
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
std::cout<<"./tcp_server 192.168.32.130 9000\n";
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
signal(SIGCHLD,sigcb);
TCPSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind(ip,port));
CHECK_RET(sock.Listen());
while(1)
{
TCPSocket clientsock;
std::string client_ip;
uint16_t client_port;
if(sock.Accept(clientsock,client_ip,client_port)==false)
{
continue;
}
std::cout<<"new client:"<<client_ip<<":"<<client_port<<std::endl;
int pid=fork();
if(pid==0)
{
while(1)
{
std::string buf;
clientsock.Recv(buf);
std::cout<<"client say:"<<buf<<std::endl;
buf.clear();
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
clientsock.Send(buf);
}
clientsock.Close();
exit(0);
}
//父进程一定要关闭这个套接字;因为父子进程数据独有
//父进程关闭对子进程无影响;不关闭会造成资源泄漏
clientsock.Close();
}
sock.Close();
return 0;
}
#include "tcpsocket.hpp"
#include
void *thr_start(void*arg)
{
//因为线程之间,共享文件描述符表,因此在一个线程中打卡的文件
//另一个线程只要能够获取到文件描述符,就能操作文件
TCPSocket *clientsock=(TCPSocket*)arg;
while(1)
{
std::string buf;
clientsock->Recv(buf);
std::cout<<"client say:"<<buf<<std::endl;
buf.clear();
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
clientsock->Send(buf);
}
clientsock->Close();
return NULL;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
std::cout<<"./tcp_server 192.168.32.130 9000\n";
return -1;
}
std::string ip=argv[1];
uint16_t port=atoi(argv[2]);
TCPSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind(ip,port));
CHECK_RET(sock.Listen());
while(1)
{
TCPSocket *clientsock=new TCPSocket();
std::string client_ip;
uint16_t client_port;
if(sock.Accept(*clientsock,client_ip,client_port)==false)
{
continue;
}
std::cout<<"new client:"<<client_ip<<":"<<client_port<<std::endl;
pthread_t tid;
pthread_create(&tid,NULL,thr_start,(void*)clientsock);
pthread_detach(tid);
}
sock.Close();
return 0;
}