大家好,欢迎来到IT知识分享网。
socket
一、六个背景知识
1、Q1:在进行网络通信时,是不是两台机器在进行通信?
答:不是的,是应用层在通信。
解析:网络协议中的下三层,主要解决的是数据安全可靠的送到远端机器。用户使用应用层软件完成数据发送和接收的。
而在使用软件的时候,必须得先启动软件,例如我们想刷抖音就需要先把抖音这个软件启动,运行以后就是进程。所以我们网络在进行通信和收发消息的时候,就是进程间通信啊!只不过是进程之间遵守了网络协议栈,用的是网络协议的系统调用接口罢了,其本质还是进程间通信。手段是两台主机通信,而目的和本质是进程之间的通信,是凌驾于应用层上的进程之间的通信。通过网络协议栈读取网络资源(共享内存资源)来让两台主机读取/存放信息。可以用读者写者这个问题来进行理解,我们的网络资源就是缓冲区的概念,读者写者在缓冲区(网络资源)中读取/存放资源。
2、端口号
端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用。
注意概念:
在公网上,ip地址能标识唯一的一台主机,端口号port能标识该主机上的唯一一个进程,故:ip:port能标识全网唯一的一个进程。
socket:客户端和服务器在进行通信的时候,客户端进程有唯一的ip地址+端口号,服务端进程也有唯一的ip地址+端口号,两者只需要进行源和目的的唯一标识即发生通信,这就是socket的概念基石。
3、端口号vs进程PID
4、目的端口怎么跟客户端绑定的呢?也就是怎么通过目的端口去找到对应的进程的呢?
5、我们的客户端,怎么知道服务器的端口号的呢?
每一个服务器的端口号必须是众所周知的精心设计的,要被客户端熟知的,我抖音自己的开发商在开发的时候,客户端和服务端的端口号都是内置的,都是被自己熟知的,所以我们使用者感觉不到这个,但是作为开发人员是要熟知服务器的端口号的。
6、一个进程可以绑定多个端口号吗?一个端口号可以被多个进程绑定吗?
显而易见:一个进程可以绑定多个端口号,但一个端口号不能被多个进程绑定。
二、两个协议
1、TCP协议(传输控制协议)
2、UDP协议(用户数据报协议)
三、网络字节序列
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
四、socket编程接口
1、socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol); // 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len); // 开始监听socket (TCP, 服务器) int listen(int socket, int backlog); // 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len); // 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
2、sockaddr结构
五、udp_socket_server代码编写
1、socket套接字的介绍
udp用的是SOCK_DGRAM,那么就是无连接不可靠的协议。
2、Log.hpp(为了打印看结果)
#pragma once #include <iostream> #include <time.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #define SIZE 1024 #define Info 0 #define Debug 1 #define Warning 2 #define Error 3 #define Fatal 4 #define Screen 1 #define Onefile 2 #define Classfile 3 #define LogFile "log.txt" class Log {
public: Log() {
printMethod = Screen; path = "./log/"; } void Enable(int method) {
printMethod = method; } std::string levelToString(int level) {
switch (level) {
case Info: return "Info"; case Debug: return "Debug"; case Warning: return "Warning"; case Error: return "Error"; case Fatal: return "Fatal"; default: return "None"; } } // void logmessage(int level, const char *format, ...) // {
// time_t t = time(nullptr); // struct tm *ctime = localtime(&t); // char leftbuffer[SIZE]; // snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), // ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, // ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // // va_list s; // // va_start(s, format); // char rightbuffer[SIZE]; // vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); // // va_end(s); // // 格式:默认部分+自定义部分 // char logtxt[SIZE * 2]; // snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer); // // printf("%s", logtxt); // 暂时打印 // printLog(level, logtxt); // } void printLog(int level, const std::string &logtxt) {
switch (printMethod) {
case Screen: std::cout << logtxt << std::endl; break; case Onefile: printOneFile(LogFile, logtxt); break; case Classfile: printClassFile(level, logtxt); break; default: break; } } void printOneFile(const std::string &logname, const std::string &logtxt) {
std::string _logname = path + logname; int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt" if (fd < 0) return; write(fd, logtxt.c_str(), logtxt.size()); close(fd); } void printClassFile(int level, const std::string &logtxt) {
std::string filename = LogFile; filename += "."; filename += levelToString(level); // "log.txt.Debug/Warning/Fatal" printOneFile(filename, logtxt); } ~Log() {
} void operator()(int level, const char *format, ...) {
time_t t = time(nullptr); struct tm *ctime = localtime(&t); char leftbuffer[SIZE]; snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec); va_list s; va_start(s, format); char rightbuffer[SIZE]; vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); va_end(s); // 格式:默认部分+自定义部分 char logtxt[SIZE * 2]; snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer); // printf("%s", logtxt); // 暂时打印 printLog(level, logtxt); } private: int printMethod; std::string path; }; // int sum(int n, ...) // {
// va_list s; // char* // va_start(s, n); // int sum = 0; // while(n) // {
// sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123); // n--; // } // va_end(s); //s = NULL // return sum; // }
3、文件描述符socket是3(套接字创建)
4、bind socket
// 2.绑定端口号bind socket struct sockaddr_in local; // 网络套接字结构体 bzero(&local, sizeof(local)); // 将该套接字结构体对象全部清零 local.sin_family = AF_INET; // 类型:ipv4 local.sin_port = htons(_port); // 端口号:是在网络中来回发送的,我发过去要让对面知道我发的端口号是什么,所以必须是网络字节序列 local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1.string->unit_32 2.来回通信对方要知道发送的ip,所以ip的unit_32必须是网络序列的 int n = bind(_socketfd, (const struct sockaddr *)&local, sizeof(local)); if (n < 0) {
log(Fatal, "bind error, erron:%d, errno string:%s", errno, strerror(errno)); exit(BIND_ERR); } log(Info, "bind sucess");
5、recvfrom和sendto
struct sockaddr_in client; socklen_t len = sizeof(client); ssize_t n = recvfrom(_socketfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len); if (n < 0) {
log(Warning, "recvfrom error"); continue; } // 简单的数据处理一下 inbuffer[n] = 0; std::string info = inbuffer; std::string echo_string = "server_echo#" + info; sendto(_socketfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr*)&client, len);
6、测试一下
7、两个问题(port和ip)
Q1.公网ip问题:
注意:0到1023是系统内定的系统端口号,一般都要有固定的应用层协议使用,例如:http对应固定端口号是80,https对应固定的端口号是443等等,所以0到1023不让普通用户去绑定,因为与本身的应用层协议是强相关的!这是因为这个应用层协议用这个端口用多了,形成了一种固定的绑定了。所以我们进行端口绑定的时候用的是1024及往上的端口号使用,我们一般用8000到9000左右,好记好用。
8、argc 和 argv的联合使用
9、总体代码
udp.hpp:
#pragma once #include <iostream> #include <string> #include <cstring> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "Log.hpp" extern Log log; uint16_t defaultport = 8080; std::string defaultip = "0.0.0.0"; enum {
SOCKET_ERR=1, BIND_ERR }; class UdpServer {
public: // 构造函数 UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip) : _socketfd(0) , _port(port) , _ip(ip) , _isrunning(false) {
} void Init() {
// 1.创建udp套接字socket _socketfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建失败 if (_socketfd < 0) {
log(Fatal, "socket create error,socketfd:%d", _socketfd); exit(SOCKET_ERR); } // 创建成功 log(Info, "socket create sucess,socketfd:%d", _socketfd); // 2.绑定端口号bind socket struct sockaddr_in local; // 网络套接字结构体 bzero(&local, sizeof(local)); // 将该套接字结构体对象全部清零 local.sin_family = AF_INET; // 类型:ipv4 local.sin_port = htons(_port); // 端口号:是在网络中来回发送的,我发过去要让对面知道我发的端口号是什么,所以必须是网络字节序列 local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1.string->unit_32 2.来回通信对方要知道发送的ip,所以ip的unit_32必须是网络序列的 int n = bind(_socketfd, (const struct sockaddr *)&local, sizeof(local)); if (n < 0) {
log(Fatal, "bind error, erron:%d, errno string:%s", errno, strerror(errno)); exit(BIND_ERR); } log(Info, "bind sucess"); } void Run() {
_isrunning = true; char inbuffer[1024]; while (_isrunning) {
struct sockaddr_in client; socklen_t len = sizeof(client); ssize_t n = recvfrom(_socketfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len); if (n < 0) {
log(Warning, "recvfrom error"); continue; } // 简单的数据处理一下 inbuffer[n] = 0; std::string info = inbuffer; std::string echo_string = "server_echo#" + info; sendto(_socketfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr*)&client, len); } } // 析构函数 ~UdpServer() {
if (_socketfd > 0) {
close(_socketfd); } } private: int _socketfd; // 网络文件描述符,表示socket返回的文件描述符 uint16_t _port; // 表明服务器进程的端口号 std::string _ip; // ip地址,任意地址绑定为0 bool _isrunning; // 判断是否运行 };
main.cc:
#include "udp.hpp" #include "Log.hpp" #include <memory> Log log; void Usage(std::string proc) {
std::cout << "\n\rUsages: " << proc << "port[1024+]\n" << std::endl; } // 以后用的是./udpserver + port int main(int argc, char *argv[]) {
if (argc != 2) {
Usage(argv[0]); exit(0); } uint16_t port = std::stoi(argv[1]); std::unique_ptr<UdpServer> svr(new UdpServer(port)); // new一个对象 svr->Init(); // 初始化 svr->Run(); // 跑起来 }
六、udp_socket_client代码编写
1、客户端要绑定吗?
系统什么时候给我绑定端口号的呢?首次发送数据的时候,客户端就进行随机绑定端口号了(即客户端代码跑到sendto的时候)。
2、代码部分
#include <iostream> #include <unistd.h> #include <string> #include <cstdlib> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> void Usage(std::string proc) {
std::cout << "\n\rUsages: " << proc << "serverip serverport\n" << std::endl; } // ./udpclient serverip serverport int main(int argc, char *argv[]) {
if (argc != 3) {
Usage(argv[0]); exit(0); } std::string serverip = argv[1]; // serverip uint16_t serverport = std::stoi(argv[2]); // serverport int socketfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字 if (socketfd < 0) {
std::cout << "socket create error" << std::endl; return 1; } std::string message; char buffer[1024]; while (true) {
// 数据 std::cout << "Please Enter# "; getline(std::cin, message); // 给谁发 struct sockaddr_in server; bzero(&server, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(serverport); server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 发送数据 -- 把数据发送到socketfd文件中,并将server信息提炼出来发送给server,可以理解成唤醒server socklen_t len = sizeof(server); sendto(socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len); // 收数据 -- 从socket文件中的数据拿出来到buffer中,并将收到的对方的个人信息进行保存到temp中 struct sockaddr_in temp; socklen_t len2 = sizeof(temp); ssize_t n = recvfrom(socketfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len2); if (n > 0) {
buffer[n] = 0; // 打印数据 std::cout << buffer << std::endl; } } close(socketfd); return 0; }
3、C/S逻辑
4、效果展示
七、改进代码(解耦合)
1、使用functional改进
2、改进使用linux命令(popen)
bool SafeCheck(const std::string& cmd) {
std::vector<std::string> word_key = {
"rm", "top", "cp", "yum", "while", "kill", "unlink" "uninstall", "top" }; for (auto &word : word_key) {
auto pos = cmd.find(word); if (pos != std::string::npos) {
return false; } } return true; } std::string ExcuteCommand(const std::string& cmd) {
// 做一个保护 if (!SafeCheck(cmd)) return "bad man"; FILE* fp = popen(cmd.c_str(), "r"); // 管道创建好,子进程创建好,子进程通过管道放到父进程 if (nullptr == fp) {
perror("popen failed"); return "error"; } std::string result; char buffer[4096]; while (true) {
char* ok = fgets(buffer, sizeof(buffer), fp); // 写到buffer缓冲区中 if (ok == nullptr) {
break; } result += buffer; } pclose(fp); return result; }
3、windows与linux进行数据收发
互通代码及成果展示
4、udp简易聊天室
udp简易聊天室
八、地址转换函数
1、介绍
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr
2、关于inet_ntoa
因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> void* Func1(void* p) {
struct sockaddr_in* addr = (struct sockaddr_in*)p; while (1) {
char* ptr = inet_ntoa(addr->sin_addr); printf("addr1: %s\n", ptr); } return NULL; } void* Func2(void* p) {
struct sockaddr_in* addr = (struct sockaddr_in*)p; while (1) {
char* ptr = inet_ntoa(addr->sin_addr); printf("addr2: %s\n", ptr); } return NULL; } int main() {
pthread_t tid1 = 0; struct sockaddr_in addr1; struct sockaddr_in addr2; addr1.sin_addr.s_addr = 0; addr2.sin_addr.s_addr = 0xffffffff; pthread_create(&tid1, NULL, Func1, &addr1); pthread_t tid2 = 0; pthread_create(&tid2, NULL, Func2, &addr2); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/111443.html

















