文章目录

复习完了基础知识之后,现在就可以开始写一个socket连接控制的对象了。
代码存在于我的github上:SocketPool

socket_obj.h

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Copyright 
// License
// Author: adairjun
// This is used to construct a socket connection obj

#ifndef SOCKETPOOL_INCLUDE_SOCKET_OBJ_H
#define SOCKETPOOL_INCLUDE_SOCKET_OBJ_H

#include <string>
#include <utility>
#include <boost/shared_ptr.hpp>
#include <glog/logging.h>

using std::string;
using std::pair;
using std::make_pair;

class SocketObj;

// 使用shared_ptr来替换SocketObj*
typedef boost::shared_ptr<SocketObj>SocketObjPtr;

class SocketObj {
public:
// 虽然作为client的话.backlog用不到,但是这里还是要写入构造函数当中
explicit SocketObj(string host, unsigned port, int backlog);
explicit SocketObj(string host, unsigned port);
explicit SocketObj(int sockFD);
virtual ~SocketObj();
void Dump() const;

string ErrorMessage();

/**
* 设置套接字的阻塞模式,nonblock为true时是非阻塞模式
*/
int SetNonBlock(bool nonblock);

/**
* 由于sockaddr_in.sin_addr.s_addr类型是unsigned类型
* 这个函数的作用就是把ip地址转换成s_addr能接受的类型
* 方便构建sockaddr_in
*/
unsigned TranslateAddress();

/**
* 封装的Bind函数
*/
bool Bind();

/**
* 封装的listen函数
*/
bool Listen();

/**
* 封装的accept函数,由于accept函数返回一个套接字,这里直接返回int
*/
int Accept();

/**
* 封装的connect函数
*/
bool Connect();

/**
* 封装的close函数
*/
bool Close();

/**
* 获取套接字
*/
int Get() const {
return sockFD_;
}

/**
* 封装的getpeername函数,返回远端地址和端口的map
*/
pair<string, int> GetPeer();

/**
* 封装的getsockname函数,返回本地地址和端口的map
*/
pair<string, int> GetSock();

private:
// 通过socket函数构建的套接字
int sockFD_;

string strHost_;
unsigned iPort_;

//本来需要一个sockaddr_in类型,来存储ip地址和端口号,但是在构造函数当中很难构造出一个完整的类型,总会报错incomplete type
//那么就无法将sockaddr_in写进成员当中

//用于listen函数的backlog_,决定内核为socket排队的最大连接个数
//从配置文件当中读取
int backlog_;

//错误信息
string strErrorMessage_;
};

#endif /* SOCKETPOOL_INCLUDE_SOCKET_OBJ_H */

相信我的头文件写的简单易懂,但是还有几点我需要说明一下:

  • 1,重载了三个构造函数:分别适配于服务器,客户端和通过描述符来构造SocketObj对象
  • 2,使用RAII的方法,用析构函数来处理close,把socket的开启与关闭和SocketObj的生命期绑定在一起。
  • 3,Dump函数是用来debug的函数
  • 4,本来我是想把sockaddr_in这些个结构体也一起封装进private当中去,但是由SocketObj的构造函数来构造sockaddr_in结构体的话,那么就需要将sockaddr_in这个结构体补充完整,而我们使用的只是其中的一部分而已,就是ip地址和端口而已,徒增无用的信息反而会影响代码。所以很多函数才会多加几行struct sockaddr_in sAddr......代码
  • strErrorMessage_则是我把错误信息封装进去了,而不是直接使用终端打印出来
  • 要说明的是TranslateAddress()这个函数,一般在客户端当中是用inet_pton(AD_INET, "127.0.0.1", &servaddr.sin_addr)这种形式来指定服务器的ip地址,能不能像服务器那样servaddr.sin_addr.s_addr=INADDR_ANY的形式呢?这个TranslateAddress()函数就是用来解决这个问题的。它把字符串直接翻译成int型,所以能够被servaddr.sin_addr.s_addr接收
  • 由于为了服务器的便捷性,我在Listen()函数当中加入了Bind()函数,以后要使用服务器的bindlisten直接使用Listen()函数就可以
  • GetPeer()函数和GetSock()函数我使用了pair<string, int>来保存信息,pair的first是ip地址,second是端口号。

socket_obj.cpp

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include "SocketPool/socket_obj.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

SocketObj::SocketObj(string host, unsigned port, int backlog)
: strHost_(host),
iPort_(port),
backlog_(backlog) {
LOG(INFO) << "consutor function HOST:[" << host << "]"
<< " PORT:[" << port << "]"
<< " BACKLOG:[" << backlog << "]";
sockFD_ = -1;
}

SocketObj::SocketObj(string host, unsigned port)
: strHost_(host),
iPort_(port) {
LOG(INFO) << "consutor function HOST:[" << host << "]"
<< " PORT:[" << port << "]";
backlog_ = 0;
sockFD_ = -1;
}

SocketObj::SocketObj(int sockFD)
: sockFD_(sockFD) {
strHost_ = "";
iPort_ = 0;
backlog_ = 0;
}

SocketObj::~SocketObj() {
Close();
}

void SocketObj::Dump() const {
printf("\n=====SocketObj Dump START ========== \n");
printf("sockFD_=%d", sockFD_);
printf("strHost_=%s", strHost_.c_str());
printf("iPort_=%d", iPort_);
printf("backlog_=%d", backlog_);
printf("strErrorMessage_=%d", strErrorMessage_.c_str());
printf("\n===SocketObj DUMP END ============\n");
}

string SocketObj::ErrorMessage() {
return strErrorMessage_;
}

int SocketObj::SetNonBlock(bool nonblock) {
int flag = fcntl(sockFD_, F_GETFL, 0);
if (nonblock) {
// 设置成非阻塞模式
return fcntl(sockFD_, F_SETFL, flag | O_NONBLOCK);
} else {
//设置为阻塞模式
return fcntl(sockFD_, F_SETFL, flag & ~O_NONBLOCK);
}
}

unsigned SocketObj::TranslateAddress() {
if (strHost_ == "")
return INADDR_ANY;
//return inet_addr(strHost_.c_str());
//使用gethostbyname比使用inet_addr更好,从函数名上看比较清晰
//而且还能根据域名来使用
struct hostent *pstrHost_ = gethostbyname(strHost_.c_str());
if (pstrHost_ == NULL) {
return inet_addr(strHost_.c_str());
}
return *(int*)(pstrHost_->h_addr);
}

bool SocketObj::Bind() {
Close();
sockFD_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockFD_ == -1) {
strErrorMessage_ = "can't bind, because sockFD_ = -1";
return false;
}
struct sockaddr_in sAddr;
memset(&sAddr, 0, sizeof(sAddr));
sAddr.sin_addr.s_addr = TranslateAddress();
sAddr.sin_family = AF_INET;
sAddr.sin_port = htons(iPort_);
if (bind(sockFD_, (struct sockaddr*)&sAddr, sizeof(sAddr)) != 0) {
Close();
strErrorMessage_ = "bind != 0";
return false;
}
return true;
}

bool SocketObj::Listen() {
Close();
Bind();
if (listen(sockFD_, backlog_) != 0) {
Close();
strErrorMessage_ = "can't listen, because listen() != 0";
return false;
}
return true;
}

int SocketObj::Accept() {
if (sockFD_ == -1) {
strErrorMessage_ = "can't accept, because sockFD_ = -1";
return -1;
}
struct sockaddr_in sAddr;
socklen_t length = sizeof(sAddr);
int customFD = accept(sockFD_, (struct sockaddr*)&sAddr, &length);
return customFD;
}

bool SocketObj::Connect() {
Close();
sockFD_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockFD_ == -1) {
strErrorMessage_ = "can't connect, because sockFD_ = -1";
return false;
}
struct sockaddr_in sAddr;
memset(&sAddr, 0, sizeof(sAddr));
sAddr.sin_family = AF_INET;
sAddr.sin_port = htons(iPort_);
sAddr.sin_addr.s_addr = TranslateAddress();
if (connect(sockFD_, (struct sockaddr*)&sAddr, sizeof(sAddr))<0) {
strErrorMessage_ = "can't connect, because connect()<0";
Close();
return false;
}
return true;
}

bool SocketObj::Close() {
if (sockFD_ != -1) {
close(sockFD_);
sockFD_ = -1;
}
return true;
}

pair<string, int> SocketObj::GetPeer() {
if (sockFD_ == -1) {
strErrorMessage_ = "can't GetPeer(), because sockFD_ = -1";
return make_pair("", 0);
}
struct sockaddr_in sAddr;
socklen_t length = sizeof(sAddr);
if(getpeername(sockFD_, (struct sockaddr*)&sAddr, &length) != 0) {
strErrorMessage_ = "getpeername != 0";
return make_pair("", 0);
}
string ipAddr = inet_ntoa(sAddr.sin_addr);
int port = ntohs(sAddr.sin_port);
return make_pair(ipAddr, port);
}

pair<string, int> SocketObj::GetSock() {
if (sockFD_ == -1) {
strErrorMessage_ = "can't GetSock(), because sockFD_ = -1";
return make_pair("", 0);
}
struct sockaddr_in sAddr;
socklen_t length = sizeof(sAddr);
if(getsockname(sockFD_, (struct sockaddr*)&sAddr, &length) != 0) {
strErrorMessage_ = "getsockname != 0";
return make_pair("", 0);
}
string ipAddr = inet_ntoa(sAddr.sin_addr);
int port = ntohs(sAddr.sin_port);
return make_pair(ipAddr, port);
}

有关Accept函数,我本来想的是将返回的socket描述符包装成一个SocketObj对象的智能指针,但是后来执行的时候出了问题,详情请参见我的下一篇博客《socket连接池SocketPool分析(五):Accept函数,进退维谷的困境》

文章目录