使用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目录下的
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进行编译首先打卡这个工程路劲
编写一个makefile
简单说下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)
启动make start
这时候会出现很多错误,
原因:
linux,用不到windows的库文件,也不需要初始化
,还有一些api不一致。
解决方案:
#ifdef WIN32 如果是 WIN32情况下调用哪些
#else 就是不再windows环境下用到的
#else
man socket 查看需要的头文件
用起来很方便
在红色部分,填写linux需要的东西,这个东西会
在编译的时候就会执行,他发现不是windows就是会执行#else
里的代码.
其他的也是一样的。 clossocket 缓冲 close就行
然后就可以编译成功了。
make start编译生成一下
然后make run执行一下
启动另外一个linux测试一下
telnet 192.168.1.125 8013 测试连接一些
然后左边我们看到把
连接的用户的 IP和端口都打印了。
基本流程都有了,后面的话,只需要照着编译就行。
我们开始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编译执行一下
在发送端连接后发送数据
服务器收到数据通信成功
循环接收用户的信息,在死循环里接收,如果收到的数据<=就退出
如果用户发送的里面有 "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编译执行一下
在发送端连接后发送数据
多线程并发处理 直接用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自己清理
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 才可以编译成功
还需要添加 -lpthread
上面的有点问题
$(CC) $(OBJ) -o $(EXEC) -std=c++11 -lthread
本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/1947841,如需转载请自行联系原作者