目录
看这篇文章前,先把有关udp和一些网络基础看了(最好敲一下代码)
一.编程前的一些基础
1.tcp是面向连接的,面向字节流的,在通信前要先建立连接(将服务端设置为监听状态(listen),服务端等待连接的到来(accept),客户端主动建立连接(connect),建立连接后就可以通信(send,recv)了)
二.tcp网络编程
1.一个服务器只能有一个客户端连接(下面代码)
Socket.hpp
cpp
#pragma once
#include<iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<cstring>
#include<string>
#include <unistd.h>
#define Convert(addr) ((struct sockaddr*)addr)
enum
{
SocketError = 1,
BindError,
ListenError,
};
const static int defaultbackflag = 5;
const int defaultsockfd = -1;
//简单解释下:Socket.hpp用了继承,多态的知识
//我们将创建socket,bind, listen, accept, connect等方法都单独封装起来
class Socket
{
public:
Socket(){}
~Socket(){}
virtual void CreateSocket() = 0; //创建socket套接字
virtual void BindSocket(uint16_t port) = 0; //绑定套接字
virtual void ListenSocket(int backflag = defaultbackflag) = 0; //将服务端设置为监听状态
virtual Socket* AcceptConnection(std::string *peerip, uint16_t *peerport) = 0; //服务端接收客户端发来的连接请求
virtual bool ConnectServer(std::string& serverip, uint16_t serverport) = 0; //客户端准备连接服务端
virtual int GetSockfd() = 0; //获取sockfd
virtual void SetSockfd(int sockfd) = 0;
virtual void CloseSockfd() = 0; //关闭文件描述符
public:
//将服务端设置为监听状态 //tcpserver.hpp调用
void BuildListenSocketMethod(uint16_t port, int backflag = defaultbackflag)
{
CreateSocket();
BindSocket(port);
ListenSocket(backflag);
}
//客户端去连接服务端 //tcpclient.hpp调用
bool BuildConnectSocketMethod(std::string& serverip, uint16_t serverport)
{
CreateSocket();
return ConnectServer(serverip, serverport);
}
void BuildNormalSocketMethod(int sockfd)
{
SetSockfd(sockfd);
}
};
//继承并将具体方法实现
class TcpSocket : public Socket
{
public:
TcpSocket(int sockfd = defaultsockfd):_sockfd(sockfd){}
void CreateSocket() override
{
//创建socket套接字
//tcp用SOCK_STREAM,
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd < 0)//失败
{
exit(SocketError);
}
}
void BindSocket(uint16_t port) override
{
//方法同udp
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, Convert(&local), sizeof(local)); //绑定本机socket
if(n != 0) exit(BindError);
}
void ListenSocket(int backflag = defaultbackflag) override
{
//设置服务端为监听状态
int n = listen(_sockfd, backflag);
if(n != 0) exit(ListenError);
}
//peerip,peerport是输出型参数,目的是获取有关client的一些信息,并为我们所用
Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) override
{
//client的一些信息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//服务端获取客户端的connect请求,进行连接(此函数阻塞等待)
//accept成功会返回一个新的文件描述符(>0),我们使用新的文件描述符进行通信
int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
if(newsockfd < 0) return nullptr;
*peerip = inet_ntoa(peer.sin_addr);
*peerport = ntohs(peer.sin_port);
Socket* s = new TcpSocket(newsockfd);
return s;
}
bool ConnectServer(std::string& serverip, uint16_t serverport) override
{
//客户端发送连接请求
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); //更安全
//或者server.sin_addr.s_addr = inet_addr(serverip.c_str());
//失败-1,并且我们发起连接时,client会被os自动地进行bind,我们不需要自己调用bind函数
int n = ::connect(_sockfd, Convert(&server), sizeof(server));
if(n == 0) return true;
else return false;
}
int GetSockfd() override
{
return _sockfd;
}
void SetSockfd(int sockfd) override
{
_sockfd = sockfd;
}
void CloseSockfd() override //关闭文件描述符
{
if(_sockfd > defaultsockfd)
{
close(_sockfd);
}
}
private:
int _sockfd;
};
TcpServer.hpp
cpp
#pragma once
#include<iostream>
#include"Socket.hpp"
class TcpServer
{
public:
TcpServer(uint16_t port) :_port(port), _listensocket(new TcpSocket())
{
_listensocket->BuildListenSocketMethod(_port, defaultbackflag); //设置为监听状态
}
void Loop()
{
char bufferin[1024];
std::string peerip;
uint16_t peerport;
Socket* s = _listensocket->AcceptConnection(&peerip, &peerport); //服务端接收客户端发来的连接请求
if(s == nullptr) exit(0);
std::cout << "accept success" << std::endl;
while(true) //接收client发的信息
{
ssize_t n = recv(s->GetSockfd(), bufferin, sizeof(bufferin)-1, 0); //接收client发送的信息
//ssize_t n = (s->GetSockfd(), bufferin, sizeof(bufferin)-1);
if(n > 0)
{
bufferin[n] = 0;
std::string ret;
ret += peerip;
ret += " ";
ret += std::to_string(peerport);
std::cout << ret << " " << "client say#" << bufferin << std::endl;
}
else
{
break;
}
}
}
~TcpServer()
{
delete _listensocket;
}
private:
int _port;
Socket* _listensocket;
};
TcpServerMain.cc
cpp
#include<iostream>
#include<memory>
#include"TcpServer.hpp"
//./tcpserver 8888
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cout << "Usage:" << "./tcpserver " << "serverport" << std::endl;
}
uint16_t localport = std::stoi(argv[1]);
std::unique_ptr<TcpServer> svr(new TcpServer(localport));
svr->Loop();
return 0;
}
TcpClientMain.cc
cpp
#include<iostream>
#include "Socket.hpp"
//./tcpclient 127.0.0.1 8888
int main(int argc, char* argv[])
{
if(argc != 3)
{
std::cout << "Usage:" << "./tcpclient " << "serverip serverport" << std::endl;
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
Socket* s = new TcpSocket();
if(!s->BuildConnectSocketMethod(serverip, serverport)) //向服务端申请建立连接
{
std::cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;
}
std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;
std::string buffer;
while(true)//发信息给服务端
{
std::getline(std::cin, buffer);
std::cout << "Enter:" << std::endl;
ssize_t n = send(s->GetSockfd(), buffer.c_str(), buffer.size(), 0); //向服务端发送信息
//ssize_t n = write(s->GetSockfd(), buffer.c_str(), buffer.size());
if(n < 0)
{
break;
}
}
s->CloseSockfd();
return 0;
}
2.一个服务器可以有多个客户端连接(多线程)
server端创建多个线程,每个线程与不同的client端建立连接。