1.前言
上一章我们已经基本学会了Linux
系统的基本使用方法,说白了就是需要记忆大量的命令,但也要讲究技巧。
而Linux系统应用范围最广的地方其实是服务器,因为Linux系统可以没有图形化界面,内核可以相当小,性能也非常好,最重要的是开源免费。
这些优势就导致,当今世界上绝大多数服务器,都是在Linux系统上开发的,而开发一个高性能的服务器,C/C++语言自然也就是不二选择。
也因此C/C++语言的一个大的就业方向就是Linux服务器开发。
2.建立项目
首先还是建立项目吧,想要高效率的开发软件,IDE肯定必不可少,所以还是采用VS开发。
还是Linux控制台项目:
注意要打开ubuntu终端,开启ssh服务:
sudo service ssh start
这样才能正常写代码,否则VS里面会报一大堆错误,找不到文件什么的。
然后运行一下,如果能正常编译运行就没问题:
如果出现VS报错,找不到文件什么的,这很正常,毕竟的通过网络传输实现的跨平台编程,不确定因素有很多。
所以一定要确保linux平台ssh服务是开启状态,并且VS可以正常连接上去。
可以先试着编译以下,只要编译没错误,就说明没问题,右键VS里面任意区域,选择重新扫描文件、项目,一般就能恢复识别。
3.Tcp编程
3.1 服务器
首先我们还是来写一个Tcp协议的聊天软件,还记得在Windows系统上如何写Tcp吗?
不记得了一定要返回重新看一看,因为网络编程的逻辑都差不多,所以我这里不会再重复赘余的讲解。
过程大致相同,但个人觉得,Linux开发起来更加简洁,首先就是在Linux系统上开发服务器,不需要加载动态库什么的,包含头文件就可以直接使用。
其次就是,Linux编程不像Windows那样,定义很多自定义类型。
首先看一下我们需要的头文件:
#include <cstdio> //等价于stdio.h,这是C++的命名规则
#include<sys/socket.h> //众多我们要使用的网络函数,比如bind,socket,connect等等
#include<arpa/inet.h> //一些我们会使用到的网络变量,比如地址sockaddr_in 这个结构体就在该头文件中
#include <unistd.h> //包含众多常见Linux API,比如本文要使用的close函数,因为socket在linxu里面也是一个文件,所以关闭文件统一使用close函数
#include<string.h> //包含一些基本的字符串操作函数,比如strcmp,比较字符串
有了上面的头文件,我们就可以来写一个服务器函数:
void Server() {
int sockListen = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(sockListen, (sockaddr*)&addr, sizeof(addr));
if (ret == -1) {
printf("绑定失败!");
return;
}
listen(sockListen, 5);
sockaddr_in cliAddr;
socklen_t len = sizeof(cliAddr);
int clisock = accept(sockListen, (sockaddr*)&cliAddr, &len);
if (clisock == -1) {
printf("接收客户端错误");
return;
}
printf("客户端已经连接!\n");
while (1) {
char buf[0xFF]{};
size_t len=recv(clisock, buf, 0xFF, 0);
if (len <= 0) {
printf("客户端已经断开连接!\n");
break;
}
send(clisock, buf, len, 0);
}
close(sockListen);
close(clisock);
}
步骤基本与windows
网络编程中相同,不同之处就是,不用自己加载网络库,关闭套接字不再使用closesocket
,而是close
函数。
注意,socket
返回的是一个int
值,因为linux
遵循万物皆文件的思想,所以都是直接使用数字代表一个文件。
包括
linux
中的普通文件操作API
等等都是如此,但由于C/C++
语言是跨平台的,所以你完全可以继续使用以前的C/C++
方式操作Linux
系统中的文件。
还有一些新的类型,比如socklen_t
等等,还是老方法,在VS里面直接右键看其真实类型是什么即可,之所以要使用这个变量类型,单纯是因为后面的函数参数是这个类型。
上面的代码就实现了一个回声服务器的基本功能
所谓回声服务器,就是你发给服务器什么内容,服务器都会原封不动的发送回来。
这就不需要你手动去服务器程序来给客户端发送消息了,这适用于测试网络是否能够正常通信。
3.2 客户端
上面写了一个服务器的函数,现在再来写一个客户端的函数,相比较而言会更加简单:
void Client() {
int sockCli = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockCli, (sockaddr*)&addr, sizeof(addr));
if (ret == -1) {
printf("连接服务器失败!");
return;
}
char buf[0xFF];
while (1) {
printf("请输入信息:");
scanf("%s",buf);
send(sockCli, buf, strlen(buf), 0);
memset(buf, 0, 0xFF);
ssize_t len=recv(sockCli, buf, 0xFF, 0);
printf("服务器:%d|%s\n", len,buf);
}
close(sockCli);
}
步骤基本都差不多,完成了Windows
网络编程学习的,这种应该随便自己看看都能看懂,我也就不多说了。
3.3 完整代码
上面写了两个函数,而没有写在两个项目里面,就是为了方便看而已:
#include <cstdio>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<string.h>
void Server() {
int sockListen = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(sockListen, (sockaddr*)&addr, sizeof(addr));
if (ret == -1) {
printf("绑定失败!");
return;
}
listen(sockListen, 5);
sockaddr_in cliAddr;
socklen_t len = sizeof(cliAddr);
int clisock = accept(sockListen, (sockaddr*)&cliAddr, &len);
if (clisock == -1) {
printf("接收客户端错误");
return;
}
printf("客户端已经连接!\n");
while (1) {
char buf[0xFF]{};
size_t len=recv(clisock, buf, 0xFF, 0);
if (len <= 0) {
printf("客户端已经断开连接!\n");
break;
}
send(clisock, buf, len, 0);
}
close(sockListen);
close(clisock);
}
void Client() {
int sockCli = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockCli, (sockaddr*)&addr, sizeof(addr));
if (ret == -1) {
printf("连接服务器失败!");
return;
}
char buf[0xFF];
while (1) {
printf("请输入信息:");
scanf("%s",buf);
send(sockCli, buf, strlen(buf), 0);
memset(buf, 0, 0xFF);
ssize_t len=recv(sockCli, buf, 0xFF, 0);
printf("服务器:%d|%s\n", len,buf);
}
close(sockCli);
}
int main(int argc,char *argv[])
{
if (argc == 1) {
printf("请输入参数!");
return -1;
}
if (strcmp("s", argv[1]) == 0) {
printf("服务器开启!\n");
Server();
}
else if(strcmp("c", argv[1]) == 0) {
printf("客户端开启!\n");
Client();
}
else {
printf("无效参数!\n");
}
}
通过main
函数传入的参数,来判断使用哪一个函数,这样就实现了一个程序既可以当服务器,又可以当客户端!
最后点击生成即可。
3.4 运行测试
注意,linux系统是可以同时开启多个终端的,比如我这里就开启了两个终端,都来到了对应的程序生成目录中。
我们先传入参数"s"
开启服务器,再传入参数"c"
在另一个终端开启客户端:
此时就能完美运行,客户端发送的任何消息都会立马收到服务器的回应。
4.UDP编程
学会了TCP
编程,UDP
就会简单很多,因为它还省略很多步骤,只需要换一些参数即可。
这里直接上代码:
#include <cstdio>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<string.h>
void Server() {
int sockListen = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(sockListen, (sockaddr*)&addr, sizeof(addr));
if (ret == -1) {
printf("绑定失败!");
return;
}
sockaddr_in cliAddr;
socklen_t aLen= sizeof(cliAddr);
while (1) {
char buf[0xFF]{};
size_t len=recvfrom(sockListen, buf, 0xFF, 0,(sockaddr*)&cliAddr,&aLen);
if (len <= 0) {
continue;
}
sendto(sockListen, buf, len, 0, (sockaddr*)&cliAddr, aLen);
}
close(sockListen);
}
void Client() {
int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
char buf[0xFF];
socklen_t alen = sizeof(addr);