Linux网络编程(3)——套接字编程(1)TCP协议实现

源IP地址—目的地址:来自哪,到哪去

端口号:发往那个端口的数据应该由哪个进程来处理

一个端口只能被一个进程占用;一个进程可以多个端口占用。

每条数据有源端口号,目的端口号。


传输层协议选择:

有连接 VS 无连接:

有连接:双方必须都同意,才可以进行数据传输(打电话,必须等对方接通才能通话)

无连接:双方不一定都同意,也可以进行数据传输(发QQ,无论在不在线,你条件允许都能发过去)

可靠传输 VS 不可靠传输:

可靠传输:不一定是100%发送成功,毕竟网线断了不可能发送成功。是指发送成功,自己知道;发送失败,自己也知道。

不可靠传输:发送失败或成功,自己也不知道。


网络字节序:默认大端存储。

字节序:CPU在内存中对数据进行存取的顺序。

主机字节序:

大端字节序:高位存低地址

小端字节序:低位存低地址

注:主机字节序取决CPU架构:X86_小端        MIPS_大端

网络字节序 和 主机字节序 的转换:

#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntonl(uint32_t netlong);

uint16_t ntonl(uint16_t netlong);

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。 


socket 编程接口:(注socket 本质上是文件描述符)

  • int socket(int domain, int type, int protocol);

创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

参数:

domain:地址域

type:套接字类型

protocol:0就好了


  • int bind(int socket, const struct sockaddr *address, socklen_t address_len);

绑定端口号 (TCP/UDP, 服务器) ——把文件和端口关联在一起。(客户端也是能用的但是几乎不用)

确实是能用端口号关联,更准确地说是和socket 关联在一起(一个进程可以关联多个端口,一个端口关联一个进程)

 


注:什么叫服务端,客户端?(主动的一方就是客户端,被动的一方是服务端。二者可以随时转变的)

  • int listen(int socket, int backlog);

开始监听socket (TCP, 服务器) —服务器来使用(手机开机信号良好)

参数:

socket:文件描述符

backlog:被动的socket,把socket 编程服务器 socket ,等待别人和连接通话


  • int accept(int socket, struct sockaddr* address,socklen_t* address_len);

接收请求 (TCP, 服务器) —服务器来使用(别人打来电话,按下接听键)

参数:

socket:文件描述符

struct sockaddr* address:对端的 ip 地址,端口号。

返回值:是一个socket,这非常关键


  • int connect(int sockfd, const struct sockaddr *addr ,socklen_t   addrlen);

建立连接 (TCP, 客户端)—客户端(你给别人拨通电话)

参数:

sockfd:文件描述符

struct sockaddr* address:对端的 ip 地址,端口号。


 这是队列图。

发送/接收 

 1、read/write也可以读写socket(TCP,不能针对UDP的socket)

2、recv/send(TCP,用法和read/write很相似,但是功能更丰富)

3、recvfrom/sendto(UDP)

代码示例: 

SOCKET:

//封装的形式实现

#include <stdio.h>
#include <cstdio>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <cstringt>
#include <sys/socket.h> //socket相关
#include <netinet/in.h>
#include <arpa/inet.h>//htons()
#include <cstringt> //sendto()  


class TcpSocket
{
public:
	TcpSocket() :fd_(-1){


	}


	bool Socket() {
		//和 UDP 不一样是第二个函数 SOCK_STREAM 意思面向字节流
		fd_ =socket(AF_INET,SOCK_STREAM,0);
			if (fd_ < 0) {
				perror("socket");
				return false;
			}
		return true;
	}

	//给服务器
	bool Bind(const std::string& ip,uint16_t port) {
		sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr(ip.c_str());
		addr.sin_port = htons(port);
		int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
		if (ret<0) {
			perror("bind");
			return false;
		}
		return true;
	}

	//给服务器
	//进入监听状态
	bool Listen() {
		//int listen(int socket, int backlog);
	    //int socket:文件描述符
		//int backlog:队列的长度
		int ret=int listen(fd_, 10);
		if (ret<0) {
			perror("listen");
			return false;
		}
		return true;
	}

	//给服务器
	bool Accept(TcpSocket* peer, std::string& ip = NULL, uint16_t port=NULL) {
		//int accept(int socket, struct sockaddr* address, socklen_t* address_len);
		//struct sockaddr* address:对端的 ip 地址,端口号。
		//从连接队列中去一个连接到用户代码中,如果队列中没有连接,阻塞(默认行为)

		sockaddr_in peer_addr;
		socklen_t len = sizeof(peer_addr); //设置初始值
		//返回值也是一个socket,类似内场销售
		int client_socket = accept(fd_, (sockaddr*)&peer_addr, &len);
		if (client_socket < 0) {
			perror("accept");
			return false;
		}
		peer->fd_ = client_socket;
		if (ip !=NULL) {
			*ip = inet_ntoa(peer_addr.sin_addr);
		}
		if (port != NULL) {
			*port = ntohs(peer_addr.sin_port);
		}
		return true;
	}

	//给客户端+服务器
	int Recv(std::string& msg) {
		msg->clear();
		char buf[1024 * 10] = { 0 };
		ssize_t n = recv(fd_, buf, sizeof(buf)-1, 0);
		//返回值成功:返回读到的字节数;失败,返回-1,如果是对端关闭了socket,但会结果是 0 。
		if (n<0) {
			perror("recv");
			return -1;
		}
		else if (n==0)
		{
			return 0;

		}
		msg->assign(buf);  //读到的数据赋值到msg中
		return 1;
		
	}

	//给客户端+服务器
	bool Send(const std::string& msg) {
		ssize_t n = send(fd_, msg.c_str(), msg.size(), 0);
		if (n < 0) {
			perror("send");
			return false;
		}
		return true;
	}

	//给客户端
	bool Connect(const 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());
		int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));
		if (ret < 0) {
			perror("connect");
			return false;
		}
		return true;
	}

	bool Close() {
		if (fd_ != 1) {
			close(fd_);
		}
		return true;
	}

private:
	int fd_;
};

服务器:

//通用的TCP 服务器
#pragma once

#include "TCPSOCKET.hpp"
#include <functional>
#include <cassert>
typedef std::function<void(const std::string&, std::string*)> Handler;

#define CHECK_RET(exp)if(!(exp)) {\
	return false; \
}

class TcpServer
{
public:
	TcpServer();
	~TcpServer();
	bool Start(const std::string& ip, uint16_t port , Handler handler) {
		//1.创建socket
		CHECK_RET( listen_sock_.Socket());

		/* 等价与:
		bool ret = listen_sock_.Socket();
		if (! ret) {
			return false;
		}*/

		//2.绑定端口号
		CHECK_RET(listen_sock_.Bind(ip, port));
		// listen_sock_ :负责拉客。把客户端的连接建立好,建立好之后,client_sock来处理。

		//3.开始监听
		CHECK_RET(listen_sock_.Listen());
		printf("启动成功!");
		//4.主循环
		while (true) {
			//5.通过Accept 获取连接
			TcpSocket client_sock;
			std::string ip;
			uint16_t port;
			bool ret = listen_sock_.Accept(&client_sock,&ip,&port);
			if (!ret) {
				continue;
			}

			//6.进行具体的沟通。 
			//因为要连接,所以要多次沟通,打电话彼此之间说很多,反反复复沟通。
			while (true) {
				std::string req;
				int r =client_sock.Recv(&req);
				if (r<0) {
					continue;
				}
				if (r == 0 ) {
					//对端关闭 //对面挂电话了
					client_sock.Close();
					printf("[%s:%d]对端关闭连接!\n",ip.c_str(),port);
					break;
				}
				if (r>0) {
					//根据请求计算响应
					std::string resp;
					handler(req, &resp);
					//响应写回客户端
					client_sock.Send(resp);
				}
			}
		}
	}

private:
	TcpSocket listen_sock_;
};

客户端

#include "TCPSOCKET.hpp"

class TcpClient {
public:
	TcpClient() {
		sock_.Socket();
	}
	~TcpClient() {
		sock_.Close();
	}

	bool Connect(const std::string& ip, uint16_t port) {
		return sock_.Connect(ip,port);
	}

	int Recv(std::string* msg) {
		return sock_.Recv(msg);
	}

	bool Send(const std::string& msg) {
		return sock_.Send(msg);
	}
	
private:
	TcpSocket sock_;
};

TCP只能英文翻译示例代码:

服务器:

#include "封装TCP服务端.hpp"
#include <isunordered_map>
int main() {
	TcpServer server;
	std::isunordered_map<std::string, std::string> dict;
	dict.insert(std::make_pair("heallo", "你好"));
	dict.insert(std::make_pair("world", "世界"));
	dict.insert(std::make_pair("bit", "比特"));
	server.Start("0.0.0.0", 9090, [](const std::string& req, std::string* resp) {
		auto it = dict.find(req);
		if (it == dict.end()) {
			*resp = "未找到";
		}
		else {
			*resp = it->second;
		}
	});
	system("pause");
	return 0;
}

客户端:

#include "封装的TCP客户端.hpp"
 
int main(int argc,char* argv[]) {
	if (argc !=2) {
		printf("Usage ./dict_client [ip]\n");
		return 1;
	}
	
	TcpClient clicent;
	bool ret = clicent.Connect(argv[1], 9090);
	if (!ret) {
		return 1;
	}
 	while (true) {
 		//从标准输入数据
		printf("请输入要查询的单词:\n");
		fflush(stdout);
		char req[1024] = { 0 };
		scanf("%s", req);

		//把读到的数据给服务器 
		clicent.Send(req);
		std::string resp;

		//读取服务器响应的结果
		clicent.Recv(&resp);

		//把响应的结果打印到标准输出上
		printf("resp: %s\n", resp.c_str());
 	}
 	system("pause");
 	return 0;
 }

问题:打开一个新的客户端,之后的就没用了,第二个listen_sock 不会被取到,之前的就取到了不会变了。核心问题在于Accept 只被调用了一次。第一次Accept之后进入一个循环,循环一直没结束,Accept没有被反复调用到,后过来的客户端都在队列中排队,一直得不到处理。就要想办法更快速得调用到Accept。

解决方式:多进程~~~多线程~~~~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值