赞
踩
本文是我的学习笔记,学习路线跟随Github开源项目,链接地址:30dayMakeCppServer
#include <iostream> // sys(系统),socket(套接字),这个还是挺好理解的 #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> int main(){ // 创建一个套接字描述符,这个描述符本质上就是一个Linux的文件描述符 int socketfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; // s_addr就是用来存储32位IPV4地址的 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 开启服务器的8888端口,问题是这个hton是什么 // hton是一个将主机字节序(host byte order)的端口号转换为网络字节序(network byte order) serv_addr.sin_port = htons(8888); /* bind首先绑定套接字的文件描述符 后者将传入的serv_addr强制转换为一个sockaddr类型的指针 也就是将sockaddr_in强制转换为sockaddr 这是因为后者是通用套接字结构体,而前者是专属于IPv4的 由此可知:网络套接字实际上是基于通用套接字的 */ bind(socketfd, (sockaddr*)&serv_addr, sizeof(serv_addr)); // 后者是一个常量,表示监听队列的最大长度 listen(socketfd, SOMAXCONN); struct sockaddr_in clnt_addr; /* 初始化一个socklen_t类型的变量,其值为clnt_addr的大小 这个类型是一个无符号整型,用于表示套接字地址结构体的长度 */ socklen_t clnt_addr_len = sizeof(clnt_addr); bzero(&clnt_addr, clnt_addr_len); int clnt_sockfd = accept(socketfd, (sockaddr*)&clnt_addr, &clnt_addr_len); /* inet_ntoa是将将一个 32 位的 IPv4 地址从网络字节序转换为点分十进制的IP地址字符串 ntohs用于将网络字节序转换为主机字节序 在这里就是将看不懂的内容转换为我们能看得懂的东西 */ printf("new client fd %d IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port)); return 0; }
基本所有的知识点我都写在其中了,有些基础性的东西需要大家自己去学习”计算机网络“的相关知识,至于为什么在使用accept等函数的时候,需要将sockaddr_in转换成sockaddr,这点需要看书:游双的《Linux高性能服务器编程》,在其中的第五章第一节:socket地址API中有详细讨论。
接下来我们看看其中使用到的头文件:sys/inet.h和arpa/inet.h。
这个头文件是网络编程的核心头文件之一,它包含了一些用于网络贬称搞到常量、数据结构和函数原型。这里指出一些常用的:
与网络通信的一系列API都在其中:
这些函数并不是sys/socket.h中的全部,这里只写了比较基础的、常用的,还有很多的内容在netinet/in.h头文件中。
在上面,我们提到了头文件netinet/in.h,这个头文件包含在arpa/inet.h中,因此我们使用了这个头文件就可以不用再次包含。它也包含了许多网络通信相关的常量、结构体、函数。
server.cpp:
#include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include "util.h" int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); errif(sockfd == -1, "socket create error"); struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(8888); errif(bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket bind error"); errif(listen(sockfd, SOMAXCONN) == -1, "socket listen error"); struct sockaddr_in clnt_addr; socklen_t clnt_addr_len = sizeof(clnt_addr); bzero(&clnt_addr, sizeof(clnt_addr)); int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len); errif(clnt_sockfd == -1, "socket accept error"); printf("new client fd %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port)); while (true) { char buf[1024]; bzero(&buf, sizeof(buf)); ssize_t read_bytes = read(clnt_sockfd, buf, sizeof(buf)); if(read_bytes > 0){ printf("message from client fd %d: %s\n", clnt_sockfd, buf); write(clnt_sockfd, buf, sizeof(buf)); } else if(read_bytes == 0){ printf("client fd %d disconnected\n", clnt_sockfd); close(clnt_sockfd); break; } else if(read_bytes == -1){ close(clnt_sockfd); errif(true, "socket read error"); } } close(sockfd); return 0; }
client.cpp:
#include <iostream> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include "util.h" int main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); errif(sockfd == -1, "socket create error"); struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(8888); errif(connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket connect error"); while(true){ char buf[1024]; bzero(&buf, sizeof(buf)); scanf("%s", buf); ssize_t write_bytes = write(sockfd, buf, sizeof(buf)); if(write_bytes == -1){ printf("socket already disconnected, can't write any more!\n"); break; } bzero(&buf, sizeof(buf)); ssize_t read_bytes = read(sockfd, buf, sizeof(buf)); if(read_bytes > 0){ printf("message from server: %s\n", buf); } else if(read_bytes == -1){ close(sockfd); errif(true, "socket read error"); } else if(read_bytes == 0){ printf("server socket disconnected!\n"); break; } } close(sockfd); return 0; }
Makefile:
# shell和Makefile有点像
build:
make client;
make server
client: client.cpp util.cpp
g++ -o client client.cpp util.cpp
server: server.cpp util.cpp
g++ -o server server.cpp util.cpp
clean:
rm -f server client
util.h:
#ifndef UTIL_H
#define UTIL_H
void errif(bool, const char*);
#endif
util.cpp:
#include "util.h"
// #include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void errif(bool condition, const char* errmsg){
if(condition){
perror(errmsg);
exit(EXIT_FAILURE);
}
}
unistd.h 是C语言标准库中的一个头文件,它提供了对POSIX(可移植操作系统接口)的访问。其中包含了许多函数和符号常量,用于进程控制、文件操作、目录操作等方面的功能。
由于在Linux中:”万事万物皆文件“,因此,我们操作进程间通信实际上就是对文件进行操作。
在上面的代码中:我们使用了该头文件中的write和read来进行网络接口的数据读写操作;使用了close来关闭网络连接文件、释放相关系统资源。
阅读server.cpp(服务端)源码,我们能够发现:我们一开始创建的socket只用于监听,而在与客户端连接的时候我们并没有使用这个socket:
// bind绑定sockfd,并使其处于监听状态
errif(bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)) == -1, "socket bind error");
errif(listen(sockfd, SOMAXCONN) == -1, "socket listen error")
// 使用accept接收客户端发来的连接请求
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
可能我们需要看看accept的声明:
extern int accept (int __fd, __SOCKADDR_ARG __addr,
socklen_t *__restrict __addr_len);
可以发现它的返回值是个int类型,而Linux中,文件描述符也正好是个int类型,在查询资料后证实,accept其实是新开了个文件描述符,用于维持和客户端的通信。我们需要通过这个文件描述符进行网络通信。
套接字可以分为两种类型:监听套接字和连接套接字,我们bind的就是监听套接字,accept所创建的文件描述符就是连接套接字,连接套接字才是真正用来网络通信的的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。