代码:
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
using namespace std;
int main() {
char buffer[1024];
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
cerr << "创建服务器接口失败" << endl;
return -1;
}
sockaddr_in server_base;
int port = 8080;
server_base.sin_family = AF_INET;
server_base.sin_port = htons(port);
server_base.sin_addr.s_addr = INADDR_ANY;
socklen_t server_base_len = sizeof(server_base);
int bind_check = bind(server_fd, (sockaddr*)&server_base, server_base_len);
if (bind_check < 0) {
cerr << "bind绑定失败" << endl;
close(server_fd);
return -1;
}
if (listen(server_fd, 5) < 0) {
cerr << "监听失败!" << endl;
close(server_fd);
return -1;
}
cout << "服务器已经全部设置成功!正在等待有效接口...." << endl;
while (true) {
sockaddr_in client_base;
socklen_t client_base_len = sizeof(client_base);
int server_to_client = accept(server_fd, (sockaddr*)&client_base, &client_base_len);
if (server_to_client < 0) {
cerr << "服务器和客户端链接失败!" << endl;
continue;
}
cout << "接进来的客户端的信息是:" << endl;
cout << "client connected:"
<< inet_ntoa(client_base.sin_addr)
<< ":"
<< ntohs(client_base.sin_port)
<< endl;
while (true) {
memset(buffer, '\0', sizeof(buffer));
int recv_check = recv(server_to_client, buffer, sizeof(buffer), 0);
if (recv_check <= 0) {
cerr << "接收失败!!" << endl;
if (recv_check == 0) {
cout << "Client disconnected." << endl;
}
else {
cerr << "Recv error." << endl;
}
break;
}
buffer[strcspn(buffer, "\n")] = 0;
cout << "信息为:" << buffer << endl;
if (strcmp(buffer, "hello") == 0) {
send(server_to_client, "hello!", 6, 0);
}
else if (strcmp(buffer, "time") == 0) {
send(server_to_client, "The time is 12:00\n", 19, 0);
}
else if (strcmp(buffer, "bye") == 0) {
send(server_to_client, "Goodbye! Closing connection.\n", 31, 0);
close(server_to_client);
cout << "已关闭通信通道,希望接受下一个客户端" << endl;
break;
}
else if (strcmp(buffer, "help") == 0) {
const char* response =
"Available commands:\n"
" hello - Greeting\n"
" time - Current time\n"
" help - Show this message\n"
" bye - Disconnect\n";
send(server_to_client, response, strlen(response), 0);
}
else {
send(server_to_client, "Unknown command. Type 'help'\n", 31, 0);
}
}
}
return 0;
}
c/c++网络编程,大部分人刚开始就被劝退的地方,最主要的一点就是”看不懂!”刚开始都是这样的,写这个文档也是为了自己加深记忆以及通过自己的理解来让大家更好的理解这部分的内容。
首先是头文件,有很多大家都不认识:
#include<sys/socket.h>
#include<netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
除了倒数第二个cstirng,c风格的字符串以外,剩下的基本上都不认识,请看图:
我们先来看一下这几个头文件所包含的内容,现在不需要去理解什么是close啊,什么是inet_ntoa啊什么的。
我们先要明确我们要实现什么功能,我们要实现的是一个服务器的创建,然后和客户端连接起来,然后通信。
来看代码,第一个buffer字符数组先不用管,让我们来看第二行,server_fd,这个表示的是一个文件描述符,ok,先别慌,什么是文件描述符呢?就是通过socket函数返回的值就是文件描述符,这里的socket函数就是对于服务器的创建,返回的文件描述符也就是专属于服务器的值,如你所见,它是一个int值,之后,当我们需要用服务器进行一些操作的时候,就需要用到这个文件描述符。那么socket的三个参数都是什么呢?这里的参数需要们记一下,第一个是地址协议族,ipv4,ipv6都是这个参数的内容,这里的AF_INET是ipv4的写法,第二个是传输的类型,这里是SOCK_STREAM,也就是流式传输,这种方式是双向的,并且数据不会丢失,安全,但是效率有点低,还有一种传输方式,可以自行去查阅,但是我们最常用的就是SOCK_STREAM传输了,第三个protocol最终使用的协议,那么这里呢,其实你已经知道了,这里的协议是ipv4,但是我们用的是SOCK_STREAM,这里对应的协议只有IPPROTO_TCP,所以我们可以填0,就不用填IPPROTO_TCP了,0的含义是自动推断,这里编译器也就直接推断出来了。好了,接下来的判断语句就是表示这个服务器到底有没有创建好,大部分情况下,当文件描述符的数值小于0的时候,基本上都是不成功的标志。
接下来就是重中之重,有点麻烦,也就是让服务器对有什么特点的客户端进行接入,嗯。。。。。还是不太严谨,假如你开了一家网吧,你限制不让未满18岁的人进入,就是这个意思!好了,言归正传,我们需要一个结构体,这个结构体存放着我们要限制的条件。
sockaddr_in,太ex了,这个结构体,一个个介绍吧,第一个sin_family是地址族协议,也就是ipv4什么的,第二个sin_port,也就是端口,当我们需要访问一个服务器的时候,需要让服务器开放一个端口,比如8080,也就是这个,但是,你这个不能直接存,这里涉及到一个主机字节序和网络字节序的差别,也就是小端序和大端序的区别,我们当前用的电脑一般都是小端序,也就是主机字节序,但是网络上传输的一般都是大端序,也就是网络字节序,所以这里需要用htons函数转换字节序才可以被成功识别。第三个是sin_addr和s_addr,这里的sin_addr其实是一个结构体,里面还有一个s_addr存放的是32位整数变量,这个意思是要绑定到那个ip地址,这里的INADDR_ANY表示绑定到本机所用可用网口,也就是说本机谁来我都连。接下来是socklen_t 这是一个类型,之后要用,用于获取server_base保存的信息的长度,但是你可能会问,sizeof返回的不是size_t么?我int同样也可以,这里请看图:
好了,介绍完之后,接下来就是bind绑定,bind呢,就是将我们的server_fd和server_base绑定在一起,就好比,我现在开了一家网吧(server_fd),接下来,我让谁进呢?噢噢噢,这部是有一份限制表嘛(server_base),好了这就应该理解了,接下来就是四个参数的解释,第一个就是文件描述符,表示我这个信息和哪一个服务器绑定起来呢?这里就是server_fd,第二个就是我们需要绑定的信息的地址,也就是结构体的地址,注意,我们需要强转这个类型,因为这里要的类型是sockaddr,但是我们的类型是sockaddr_in,其实,这里第二个参数的统一接口就是sockaddr,我们用的sockaddr_in是ipv4的专用结构体,所以这里需要转为sockaddr*,第三个参数就不用多说了,就是长度,表示这个结构体的大小是多少,防止越界。
绑定完成后,就是监听了,listen,为什么还需要listen呢,因为即使你创建了服务器,也bind了,但是他仍然没有起到监听的作用,也就是说,我接入端口,没有作用啊,你没给服务器按作用,我怎么进啊,所以,这里listen是必须的,第一个参数就是文件描述符,第二个参数就是最大监听数量,这里可以按自己需求填哦。
接下来的代码请看
int client_socket;
sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
cout << "正在等待接口...." << endl;
client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len);
if (client_socket < 0) {
cerr << "没能等到有效接口" << endl;
close(server_fd);
return 1;
}
cout << "接口已经成功接入!" << endl;
char buffer[1024] = { 0 };
read(client_socket, buffer, 1024);
cout << "消息:" << buffer << endl;
const char* response = "你好!";
send(client_socket, response, strlen(response), 0);
cout << response << " 的消息已发送!" << endl;
close(client_socket);
close(server_fd);
return 0;
}
好的,处理完服务器,我们要处理客户端了,首先还是需要一个sockaddr_in的结构体来存放客户端的信息,还有一个client_socket的int值来表示为文件描述符,当然还有socklen_t,好,我们来仔细看看accept这个函数,这个函数表示返回一个文件描述符,这个文件描述符表示服务器和客户端(也就是我们)之间的专用通信通道,之后的read,recv,send都需要这个通道,好,三个参数很简单,第一个需要连接的服务器文件描述符,第二个客户端的信息存储,第三个客户端信息机构体的大小,如果连接成功,那么,我们就可以创建一个buffer,char类型的数组,大小可以自己定义,然后我们read,直接把数据读取到,然后我们再用cout输出就行(c语言的可以用printf),之后,我们肯定要回传给客户端一个信息,也就是response,这里可以随便返回什么,只用于测试,用send函数发送给客户端,四个参数,第一个专属通信通道的文件描述符,第二个回传的信息,第三个回传信息的长度,第四个默认0。之后线close(client_socket),最后close(server_fd)。
注意,这里的client_socket等同于第一个代码块中的server_to_client。
好了,接下来你已经了解了TCP服务器和客户端的基本传输功能了,请现在看第一个代码块,你是否能够自己写出来类似的功能呢?比如,当客户端断开连接的时候,服务器不断开,并且等待下一个客户端的连接,还有不同的信息语句,服务器回传的信息不同,这些都在上面的代码中,如果有兴趣,可以自己试着实现一下,最后就是,多敲代码,多写几遍,理解为主!
5471

被折叠的 条评论
为什么被折叠?



