前言
socket套接字的有些知识在上一篇【UDP】socket套接字带你快速上手中已经讲过了,这里就不在过多的叙述了。
初识TCP
TCP的主要特点
- TCP是面向连接的传输层协议
- TCP提供可靠交付的服务
- TCP提供全双工通信
- TCP是面向字节流的
TCP socket API
socket()
- 此函数是用来创建socket文件描述符的。创建成功则返回socket文件描述符,失败则返回-1并且设置对应的错误码
- 第一个参数是用来指定通信域的,也就是选择用于通信的协议族的,这里用的是IPv4协议,参数指定就是AF_INET
- 第二个参数是用来指定通信类型的。这里所用到的是TCP,所以参数指定为SOCK_STREAM,表示面向流的传输协议
- 第三个参数暂时用不到,设置为0即可
bind()
- 此函数是用来将用户设置的IP地址和port端口号在内核中和当前进程强关联。成功返回0,失败返回-1并且设置对应的错误码
- 第一个参数是你创建好的socket文件描述符
- 第二个参数是一个struct sockaddr的结构体指针
- 第三个参数是这个sockaddr的大小
listen()
- 此函数用来声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略
- listen成功返回0,失败返回-1并且设置对应的错误码
accept()
三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到客户端连接上来
第一个参数是创建好的socket文件描述符
第二个参数是一个struct sockaddr的结构体指针
第三个参数是此结构体的大小
返回值为socket文件描述符
理解accept的返回值
accept中传入了一个socket文件描述符,但是它返回的依然是一个文件描述符,那么如何理解这两个文件描述符呢?
举个例子,假如你家有个饭店,刚好这时到了饭点了,你就派出了张三到饭店门口去拉客,此时来了一名顾客正好被张三拉进到店里去了,你又派出了李四服务员去服务这位顾客,并且让张三又返回到店门口继续拉客。
在上述例子中,张三就是我们传入accept中的socket文件描述符,是获取到的新连接的,而李四就是accept返回的socket文件描述符,是真正提供服务的。
connect()
- 在客户端中调用,用来连接服务器
- 参数形式和bind的一致,区别在于bind传入的是自己的地址,为connect传入的是对方的地址
- listen成功返回0,失败返回-1并且设置对应的错误码
tcp_client.cc
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void Usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
bool alive = false;
int sock = 0;
std::string line;
while (true)
{
if (!alive)
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << std::endl;
exit(2);
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
{
std::cerr << "connect error" << std::endl;
exit(3);
}
alive = true;
std::cout << "connect success" << std::endl;
}
std::cout << "请输入你的内容# ";
std::getline(std::cin, line);
if (line == "quit")
break;
ssize_t s = send(sock, line.c_str(), line.size(), 0);
if (s > 0)
{
char buffer[1024];
ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (s > 0)
{
buffer[s] = 0;
std::cout << "server 回应# " << buffer << std::endl;
}
else if (s == 0)
{
close(sock);
}
}
else
{
close(sock);
}
alive = false;
}
return 0;
}
tcp_server.cc
cpp
#include "tcp_server.hpp"
#include <memory>
static void Usage(std::string proc)
{
std::cout << "\nUsage" << proc << " port\n" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<Tcp_Server> svr(new Tcp_Server(port));
svr->InitServer();
svr->Start();
return 0;
}
tcp_server.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/wait.h>
#include "log.hpp"
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
char buffer[1024];
while (true)
{
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
}
else if (s == 0) // 对端关闭连接
{
logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
break;
}
else
{
logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
break;
}
write(sock, buffer, strlen(buffer));
}
close(sock);
}
class ThreadDate
{
public:
int _sock;
std::string _ip;
uint16_t _port;
};
class Tcp_Server
{
private:
const static int gbacklog = 20;
static void *threadRoutine(void *args)//必须得是static,因为不是static的话会多出一个this指针
{
pthread_detach(pthread_self());//线程分离
ThreadDate *td = static_cast<ThreadDate*> (args);
service(td->_sock, td->_ip, td->_port);
delete td;
return nullptr;
}
public:
Tcp_Server(uint16_t port, std::string ip = "")
: _port(port), _ip(ip), listensock(-1)
{
}
~Tcp_Server()
{
}
void InitServer()
{
// 1.创建socket
listensock = socket(AF_INET, SOCK_STREAM, 0);
if (listensock < 0)
{
//创建socket失败
logMessage(FATAL, "create socket error, %d:%S", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock:%d", listensock);
// 2.bind
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); //INADDR_ANY:任意IP
if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
//bind失败
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
// 3.建立连接
if (listen(listensock, gbacklog) < 0)
{
//建立连接失败
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "init server success");
}
void Start()
{
// signal(SIGCHLD, SIG_IGN);//多进程版
while (true)
{
// 4.获取连接
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
// 获取连接失败
if (servicesock < 0)
{
logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
continue;
}
// 获取连接成功
uint16_t client_port = ntohs(src.sin_port);
std::string client_ip = inet_ntoa(src.sin_addr);
logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n", servicesock, client_ip.c_str(), client_port);
// 5.开始通信
// 版本1------单进程循环版
// 缺点:一次只能处理一个客户端,处理完后才能处理下一个
// service(servicesock, client_ip, client_port);
// close(servicesock);
//版本2.0------多进程(创建子进程)
// pid_t id = fork();
// assert(id != -1);
// if(id == 0)
// {
//子进程
// close(listensock);
// service(servicesock, client_ip, client_port);
// exit(0);
// }
//父进程
// close(servicesock);
//版本2.1------多进程版本(孤儿进程)
// pid_t id = fork();
// assert(id != -1);
// if(id == 0)
// {
//子进程
// close(listensock);
// if(fork() > 0) exit(0);//子进程退出,由操作系统领养
//孙子进程
// service(servicesock, client_ip, client_port);
// exit(0);
// }
//父进程
// waitpid(id, nullptr, 0);
// close(servicesock);
//版本3------多线程版
ThreadDate *td = new ThreadDate();
td->_sock = servicesock;
td->_ip = client_ip;
td->_port = client_port;
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, td);
}
}
private:
uint16_t _port;
std::string _ip;
int listensock;
};
完整代码链接
TCP socket:TCP Socket