title: socket编程–TCP
date: 2019-07-19 14:48:20
tags: Linux网络
TCP网络编程
TCP网络编程流程
socket的API接口:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
参数:
domain:地址域
AF_INET:IPV4网路协议地址域
type:套接字类型
SOCK_STREAM 流式套接字,默认协议TCP不支持UDP
SOCK_DGRAM 数据报套接字 默认UDP,不支持TCP
protocol:协议类型
0:使用套接字默认协议
6/IPPROTO_TCP tcp协议
17/IPPROTO_TCP UDP协议
返回值:
套接字操作句柄文件描述符
失败-1;
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
参数:
sockfd: 创建套接字返回的描述符
addr: 地址信息
addrlen 地址信息长度
返回值 失败返回-1成功0
//接受数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd: 创建套接字返回的描述符
buf: 接收的数据
len: 想要接收数据的长度
flags 0默认阻塞接收
saddr: 发送端地址信息
addrlen 地址信息长度(输入输出型参数)不但指定接收多长还要保存实际接受多长
返回值:
返回实际接受长度
-1 失败
//发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd: 创建套接字返回的描述符
buf: 发送的数据
len: 想要发送数据的长度
flags 0默认阻塞发送
daddr: 目的端信息
addrlen 地址信息长度
返回值:
成功:实际发送的长度
失败: -1
Listen()
int listen(int socket, int backlog);开始监听socket (TCP, 服务器)
参数:
socket:套接字描述符
被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个 主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后 在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为 一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。
backlog:最大监听数目
如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。
Listen()函数在一个socket的句柄上监听连接。这个函数可以让sockfd参数引用的那个socket的句柄标记成一个**被动式的socket,**即所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
当进程处理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列(baklog)以跟踪这些完成的连接但服务器进程还没有接手处理(已完成连接队列)或正在进行的连接(未完成连接队列),这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
accpect()
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
ac
阻塞:
如果队列中没有等待的连接,套接字也没有被标记为Non-blocking,accept()会阻塞调用函数直到连接出现;如果套接字被标记为Non-blocking,队列中也没有等待的连接,accept()返回错误EAGAIN或EWOULDBLOCK。
connect()
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
参数sockfd
指定数据发送的套接字,解决从哪里发送的问题。内核需要维护大量IO通道,所以用户必需通过这个参数告诉内核从哪个IO通道,此处就是从哪个socket接口中发送数据。sockfd是先前socket返回的值。
参数server_addr
指定数据发送的目的地,也就是服务器端的地址。这里服务器是针对connect说的,因为connect是主动连接的一方调用的,所以相应的要存在一个被连接的一方,被动连接的一方需要调用listen以接受connect的连接请求,如此被动连接的一方就是服务器了。
参数addrlen
指定server_addr结构体的长度。我们知道系统中存在大量的地址结构,但socket接口只是通过一个统一的结构来指定参数类型,所以需要指定一个长度,以使内核在进行参数复制的时候有个有个界限。
c++封装TCP接口
include <iostream>
#include <string.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
class TcpSocket{
public:
TcpSocket():_sockfd(-1){}
void SetSockfd(int newfd){
_sockfd = newfd;
}
bool Socket(){
_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(_sockfd < 0){
perror("socket error");
return false;
}
return true;
}
bool Bind(std::string& ip,uint16_t port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(sockaddr_in);
int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
if(ret < 0){
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog = 10){
int ret = listen(_sockfd,backlog);
if(ret <0){
perror("listen error");
return false;
}
return true;
}
bool Connect(std::string& ip,uint16_t port){
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
if(ret < 0){
perror("connect error");
return false;
}
return true;
}
bool Recv(std::string& buf){
char tmp[4096] = {0};
ssize_t ret = recv(_sockfd,tmp,4096,0);
if(ret < 0){
perror("recv error");
return false;
}else if(ret == 0){
std::cout<<"perr shutdown"<<std::endl;
return false;
}
buf.assign(tmp,ret);
return true;
}
bool Accept(TcpSocket& csock,struct sockaddr_in* addr = NULL){
struct sockaddr_in _addr;
socklen_t len = sizeof(addr);
int newfd = accept(_sockfd,(struct sockaddr*)&_addr,&len);
if(newfd < 0){
perror("accept error");
return false;
}
if(addr != NULL){
memcpy(addr,&_addr,len);
}
csock.SetSockfd(newfd);
return true;
}
bool Send(std::string& buf){
int ret = send(_sockfd,buf.c_str(),buf.size(),0);
if(ret < 0){
perror("send error");
return false;
}
return true;
}
void Close(){
close(_sockfd);
}
private:
int _sockfd;
};
TCPSEVER
#include "tcpsocket.hpp"
#define CHECK_RIGHT(q) if((q == false)) {return -1;}
int main(int argc,char* argv[]){
if(argc < 3){
std::cout<<"./tcp_srv ip port"<<std::endl;
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSocket sock;
CHECK_RIGHT(sock.Socket());
CHECK_RIGHT(sock.Bind(ip,port));
CHECK_RIGHT(sock.Listen());
while(1){
TcpSocket clisock;
sockaddr_in addr;
if(sock.Accept(clisock,&addr) == false){
continue;
}
std::string buf;
clisock.Recv(buf);
std::cout<<"client say:"<<buf<<std::endl;
buf.clear();
std::cout<<"sever say"<<std::endl;
fflush(stdout);
std::cin>>buf;
}
sock.Close();
return 0;
}
TCPClient
#include "tcpsocket.hpp"
#define CHECK_RIGHT(q) if((q == false)) {return -1;}
int main(int argc,char* argv[]){
if(argc < 3){
std::cout<<"./tcp_cli ip port"<<std::endl;
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSocket sock;
CHECK_RIGHT(sock.Socket());
CHECK_RIGHT(sock.Connect(ip,port));
while(1){
std::string buf;
std::cout<<"client say"<<std::endl;
fflush(stdout);
std::cin>>buf;
sock.Send(buf);
buf.clear();
sock.Recv(buf);
std::cout<<"server say:"<<buf<<std::endl;
}
sock.Close();
return 0;
}