C++socket网络编程(跨平台)实战HTTP服务器(二)

简介:

         使用Socket创建TCP服务器


      1首先了解一下TCP

1TCP是面向连接的,必须是三次握手之后

2TCP提供可靠连接,实现丢失重传,RTT的估算物理 网卡 网线都会影响 这个丢包

3TCP通过给所发数据的每一个段管理一个序列号进行排序. 没一个包都有一个序号,由底层按照序列号发送给你

4TCP提供流量控制和拥塞控制:通过窗口拥塞窗口可以限制流量的,1000个客户TCP可以限定他能够是以多块的速度来接收你的数据TCP的连接是全双工的. 互相不干扰发送和接收是同时进行的

    

首先我们配置一下Linux环境非常简单哦

地址是:http://12158490.blog.51cto.com/12148490/1947803

Linux需要安装g++ gcc makefile



1开始写代码在windows上编码,但是我们的工程是在linux目录下的

wKioL1lpfh3hHBReAAAQ6OptOho664.png-wh_50


1初始化

1
2
3
4
5
/初始化windows网络库,这里就会载入动态库文件
所以我们要载入lib文件,可以在vs载入也可以代码
#pragma comment(lib,"ws2_32.lib")
WSADATA ws;
WSAStartup(MAKEWORD(2,2),&ws);


2 初始化完成就是创建一个socket了,socket反回的

是一个int型,是与客户建立建立的socket,第一个参数是使用的

协议是tcp/ip,应为socket还可以用来蓝牙的通信协议

第二个参数是使用TCP还是UDP这里SOCK_STREAM是TCP

第三个参数是原始套接字的时候用到的为0就行


1
2
3
4
5
6
7
int  sock = socket(AF_INET, SOCK_STREAM, 0);
if  (sock == -1)
{
printf ( "create err\n" );
return  -1;
}
printf ( "[%d]" , sock);



3绑定

sockaddr_in是一个结构体,他记录端口和ip地址以及协议族.

1需要指定端口。

2绑定本地的ip地址,如果绑定127.0.0.1就只能本地访问

这个网络,如果绑定外网ip那只能从外部访问,内部不能访问

一台机器可以有两个网卡,如果要任意都可以访问那就直接htonl(0),将ip和杜端口绑定到哪个socket,

bind()如果不等于0会失败,而且很容易失败,最好判断一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
         enum {PORT=8017}
  
     sockaddr_in sa;
     sa.sin_family = AF_INET;
     sa.sin_port = htons(PORT);
     sa.sin_addr.s_addr = htonl(0);
 
     if  (bind(sock, (sockaddr*)&sa,  sizeof (sa)) != 0)
     {
         printf ( "bind port err\n" );
         return  -2;
     }
     printf ( "bind port ok!!!\n" );


4侦听

绑定只是做了一个标识,还没有等待客户的连接.

这个函数一调用开始接收用户的连接了。第二个参数

是该套接字使用的队列长度,指定在请求队列总允许的最大请求

数,意思就是如果你接收10个连接还没有用Accept进行接待就会把新的连接扔掉。

1
listen(socket,10);


4获得连接用户信息

这个函数是获得获得用户的连接信息,他反回一个int是用来真正

和用户进行收发数据的,第一个参数之前创建的sock是和用户建立连接的,第二个参数是输出的参数,就是你穿进去之后,在里面进行赋值,

然后输出出来, 在访问结构体的成员就可以获得ip和端口了。

如果不需要ip地址和端口,只需要直接传人0就行。

这个时候就可以创建线程和用户进行通信了,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sockaddr_in sadd;
socklen_t len =  sizeof (sadd);
 
int  client = accept(sock, (sockaddr*)&sadd, &len);
 
if  (client > 0)
{
     printf ( "client connect ok\n" );
     printf ( "sockeid: %d\n" , client);
     
     //输出端口号和信息
     char  *ip = inet_ntoa(sadd.sin_addr);
     //网络字节序转本地字节序哦
     unsigned  short  port = ntohs(sadd.sin_port);
     printf ( "IP: %s\n" , ip);
     printf ( "port: %d\n" , port);
     closesocket(client);
}

一般情况下每建立一个连接,会把他传到一个新的线程当中,简单的方案是把他来的时候就创建一个新的线程复杂的方案是创建一个线程池,每来一个连接,把他扔给闲置的线程.



我们在linux进行编译首先打卡这个工程路劲

wKiom1lphByAcaDzAAAI6F4dDMs617.png-wh_50

编写一个makefile

wKioL1lphDSikCNcAAAFOp-XjBE140.png-wh_50




简单说下makefile

上面的4个类似预定义

CC是编译器

SRC是源文件

OBJ是编译的文件

EXEC编译连接可执行文件


$(OBJ)代表简单点就是使用这个预定义一样类似

start: $(OBJ)  

    $(CC) -o $(EXEC) $(OBJ)


上面依赖OBJ他会先执行这个

$(OBJ):

    $(CC) -o $(OBJ) -c $(SRC)//这里只编译


//删除只编译的文件

clean:

    rm -f $(OBJ)


//执行 可执行文件

run:

    ./$(EXEC)

wKiom1lphEmiAx0wAABCRjOB8XY461.png-wh_50



启动make start 

wKiom1lphavQ32lyAABDivf7H78455.png-wh_50

这时候会出现很多错误,

wKiom1lphd2wMjiLAABB9Y4rAnM276.png-wh_50


原因:

    linux,用不到windows的库文件,也不需要初始化

,还有一些api不一致。


解决方案: 

#ifdef WIN32  如果是 WIN32情况下调用哪些


#else    就是不再windows环境下用到的


#else


man socket 查看需要的头文件

wKioL1lphnaj5oHvAAAL8OatXSU238.png-wh_50

用起来很方便

wKiom1lphqawuhyJAABXbU3EvU0657.png-wh_50



在红色部分,填写linux需要的东西,这个东西会

在编译的时候就会执行,他发现不是windows就是会执行#else

里的代码.  

wKiom1lphsSw_lkPAAAdyDnuAUM606.png-wh_50

其他的也是一样的。 clossocket 缓冲 close就行

然后就可以编译成功了。


make start编译生成一下

然后make run执行一下

wKiom1lph2bx88nRAAA5HspBT0s739.png-wh_50


启动另外一个linux测试一下

telnet 192.168.1.125 8013 测试连接一些

然后左边我们看到把

连接的用户的 IP和端口都打印了。

wKioL1lph6fgsbCVAAGONiicwFY745.png-wh_50



基本流程都有了,后面的话,只需要照着编译就行。

我们开始recv接收客户端发送数据

第一个参数是用户和你连接之后,返回的

那个socket哦,第二个参数是存储数据的缓冲,

第三个参数是缓冲大小-1是为了留意味放/0结束符

第四个0就行了,跟系统有关的东西

返回值就是收到数据的大小

实际返回值有时候会大于1024 但是发送方会

有一些数据的切割,所以很容易造成错误

1
2
3
4
char  buf[1024];
memset (buf, 0,  sizeof (buf));
int  len = recv(client, buf,  sizeof (buf) - 1, 0);
print( "recv %s\n" ,buf);

在Linux编译执行一下


在发送端连接后发送数据

wKiom1lpi7DxK6dJAABpq1EDpx8815.png-wh_50


服务器收到数据通信成功

wKiom1lpi93D8TciAADeoDa2OAE328.png-wh_50




循环接收用户的信息,在死循环里接收,如果收到的数据<=就退出

如果用户发送的里面有 "quit"就会退出

1
2
3
4
5
6
7
8
for  (;;)
{
memset (buf, 0,  sizeof (buf));
int  recv_len = recv(client, buf,  sizeof (buf) - 1, 0);
if  (recv_len <= 0) break ;
if ( strstr (buf,  "quit" )!= NULL)  break ;
printf ( "recv %s\n" , buf);
}



在Linux编译执行一下


在发送端连接后发送数据

wKioL1lpjPOjWlzeAADMPaPMpag632.png-wh_50



多线程并发处理 直接用c++11的线程库。不需要考虑跨平台

的问题.

创建线程的地方就是,accept然后会返回的一个socket

给这个线程用


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <thread>
using  namespace  std;
class  TcpThread
{
public :
     //线程入口函数 创建一个
     void  Main()
     {
         char  buf[1024];
       for  (;;)
         {
         memset (buf, 0,  sizeof (buf));
         int  recv_len = recv(client, buf,  sizeof (buf) - 1, 0);
 
         if  (recv_len <= 0) break ;
         if  ( strstr (buf,  "quit" ) != NULL)
         {
             char  re[] =  "quit success\n" ;
             int  sendlen = send(client, re,  strlen (re)+1, 0);
 
         }
 
         int  sendlen = send(client,  "ok\n" ,4, 0);
         printf ( "recv %s\n" , buf);
 
         }
 
         closesocket(client);
         
     }
     
     //用户的socket
     int  client = 0;
     
 
};

如何创建线程呢,获得用户信息accept应该放到死循环里

应为线程需要他的返回值.




创建一个线程  需要考虑什么时候清理

几种方案 1对象复用 2自己清理



wKiom1lplQbAEFq8AAAt_2cuuJM690.png-wh_50

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
for  (;;)
{
//这里会阻塞 只有等待有新的连接 才会执行下面的代码
int  client = accept(sock, (sockaddr*)&sadd, &len);
if  (client > 0)
{
printf ( "client connect ok\n" );
printf ( "sockeid: %d\n" , client);
//输出端口号和信息
char  *ip = inet_ntoa(sadd.sin_addr);
//网络字节序转本地字节序哦
unsigned  short  port = ntohs(sadd.sin_port);
printf ( "IP: %s\n" , ip);
printf ( "port: %d\n" , port);
}
else
{
break ;
}
//创建一个线程  需要考虑什么时候清理
//几种方案 1对象复用 2自己清理
TcpThread *th =  new  TcpThread();
th->client = client;  //获得socket
//启动线程 第一个参数是入口函数的地址(函数指针)
//第二个参数调用的对象
std:: thread  sth(&TcpThread::Main,th);
//上面的调用完后 对象 会被销毁掉
//销毁不受影响 但是本线程还有一些资源没有被释放掉
// 我们可以直接让他直接释放调用,主线程不要控制子线程的处理,比如挂起啊 或者关闭
//这种操作是很危险的,因为 主线程不知道子线程运行到什么阶段 
//正常情况我们不去处理 detach()  释放主线程拥有的子线程的资源
sth.detach();
}


然后移植到linux  makefile需添加 std c++11 才可以编译成功

wKioL1lpnAjQh2oiAAAe6pcoS18046.png-wh_50

还需要添加 -lpthread

wKiom1lpnbmiMxuLAAAYUTZY-IA850.png-wh_50

上面的有点问题

$(CC) $(OBJ) -o $(EXEC) -std=c++11 -lthread



 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/1947841,如需转载请自行联系原作者





相关文章
|
3月前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
68 2
|
2月前
|
缓存 JavaScript 前端开发
高效打造跨平台桌面应用:Electron加载服务器端JS
【9月更文挑战第17天】Electron 是一个基于 Chromium 和 Node.js 的开源框架,允许使用 HTML、CSS 和 JavaScript 构建跨平台桌面应用。加载服务器端 JS 可增强应用灵活性,实现代码复用、动态更新及实时通信。通过 HTTP 请求、WebSocket 或文件系统可实现加载,但需注意安全性、性能和兼容性问题。开发者应根据需求选择合适方法并谨慎实施。
107 3
|
2天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
21 13
|
7天前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。
|
17天前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
|
29天前
|
Linux C语言 C++
vsCode远程执行c和c++代码并操控linux服务器完整教程
这篇文章提供了一个完整的教程,介绍如何在Visual Studio Code中配置和使用插件来远程执行C和C++代码,并操控Linux服务器,包括安装VSCode、安装插件、配置插件、配置编译工具、升级glibc和编写代码进行调试的步骤。
133 0
vsCode远程执行c和c++代码并操控linux服务器完整教程
|
2月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
106 1
|
29天前
|
存储 监控 NoSQL
Redis的实现二: c、c++的网络通信编程技术,让服务器处理多个client
本文讨论了在C/C++中实现服务器处理多个客户端的技术,重点介绍了事件循环和非阻塞IO的概念,以及如何在Linux上使用epoll来高效地监控和管理多个文件描述符。
23 0
|
30天前
|
JSON API 开发者
深入解析Python网络编程与Web开发:urllib、requests和http模块的功能、用法及在构建现代网络应用中的关键作用
深入解析Python网络编程与Web开发:urllib、requests和http模块的功能、用法及在构建现代网络应用中的关键作用
14 0
|
2月前
|
JSON API 开发者
Python网络编程新纪元:urllib与requests库,让你的HTTP请求无所不能
【9月更文挑战第9天】随着互联网的发展,网络编程成为现代软件开发的关键部分。Python凭借简洁、易读及强大的特性,在该领域展现出独特魅力。本文介绍了Python标准库中的`urllib`和第三方库`requests`在处理HTTP请求方面的优势。`urllib`虽API底层但功能全面,适用于深入控制HTTP请求;而`requests`则以简洁的API和人性化设计著称,使HTTP请求变得简单高效。两者互补共存,共同推动Python网络编程进入全新纪元,无论初学者还是资深开发者都能从中受益。
49 7

热门文章

最新文章