最近想要用C++写个socket的服务器,用于日常的项目开发。
不过,我是新手,那就慢慢地学习一下吧。
首先,先写一段程序,用起来先。
感谢博文: Linux下 C++调用C 实现socket网络通讯编程 - 雕戈 - ITeye技术网站
#include
using namespace std;
//head files of Linux
#include
#include //for fork and read
#include //for socket
#include //for socket
#include // for bzero
#include
void server()
{
const unsigned short SERVERPORT = 53556;
const int BACKLOG = 10; //10 个最大的连接数
const int MAXSIZE = 1024;
int sock, client_fd;
sockaddr_in myAddr;
sockaddr_in remoteAddr;
sock = socket(AF_INET, SOCK_STREAM, 0);
//create socket
if( sock == -1)
{
cerr<<"socket create fail!"<exit(1);
}
cout<<"sock :"<//bind
myAddr.sin_family = AF_INET;
myAddr.sin_port = htons(SERVERPORT);
myAddr.sin_addr.s_addr = INADDR_ANY;
bzero( &(myAddr.sin_zero), 8);
if(bind(sock, (sockaddr*)(&myAddr), sizeof(sockaddr)) ==-1 )
{
cerr<<"bind error!"<exit(1);
}
//listen
if(listen(sock, BACKLOG) == -1)
{
cerr<<"listen error"<exit(1);
}
while(true)
{
unsigned int sin_size = sizeof(sockaddr_in);
if( (client_fd = accept(sock, (sockaddr*)(&remoteAddr), &sin_size)) ==-1 )
{
cerr<<"accept error!"<continue;
}
cout<<"Received a connection from "<<static_cast<char*>(inet_ntoa(remoteAddr.sin_addr) )<//子线程
if(!fork() )
{
int rval;
char buf[MAXSIZE];
if( (rval = read(client_fd, buf, MAXSIZE) ) <0)
{
cout<<"Reading stream error!\n";
continue;
}
cout<//向客户端发送信息
const char* msg = "Hello, I am xiaojian. You are connected !";
if( send(client_fd, const_cast<char*>(msg), strlen(msg), 0) == -1)
cerr<<"send error!"<exit(0);
}
}
}
int main()
{
server();
}
#include
using namespace std;
#include
#include
#include
#include
#include
#include
void client()
{
const unsigned short SERVERPORT = 53556;
const int MAXSIZE = 1024;
const char* SERVER_IP = "115.159.90.99";
const char* DATA = "this is a client message ";
int sock, recvBytes;
char buf[MAXSIZE];
// hostent *host;
sockaddr_in serv_addr;
if( (sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
cerr<<"socket create fail!"<exit(1);
}
bzero( &serv_addr, sizeof(serv_addr) );
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVERPORT);
serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
if( connect(sock, (sockaddr*)&serv_addr, sizeof(sockaddr)) == -1)
{
cerr<<"connect error"<exit(1);
}
write(sock, const_cast<char*>(DATA), strlen(DATA) );
if( (recvBytes = recv(sock, buf, MAXSIZE, 0)) == -1)
{
cerr<<"recv error!"<exit(1);
}
buf[recvBytes] = '\0';
cout<int main()
{
client();
}
代码比较容易理解,主要是各种 API 的理解和使用。
解释一下代码:
首先看到一个结构体:sockaddr_in
,这是什么结构呢
sockaddr_in
在头文件in.h
中声明,这个头文件在/usr/include/netinet/
目录下,去一看究竟,可以找到它的声明:
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
一个比较好的解释:sockaddr和sockaddr_in的区别 - The time is passing - ITeye技术网站
(其实我并不懂,所以暂时不解释)
但是这里要提到的几句代码:
myAddr.sin_family = AF_INET;
myAddr.sin_port = htons(SERVERPORT);
myAddr.sin_addr.s_addr = INADDR_ANY;
bzero( &(myAddr.sin_zero), 8);
其中 AF_INET 定义了协议族(TCP\UDP等)。
函数名: socket(建立一个socket通信)
表头文件:#include
#include
定义函数:int socket(int domain,int type,int protocol);
函数说明: socket()用来建立一个新的socket,也就是向系统注册,通知系统建立一通信端口。参数domain 指定使用何种的地址类型,完整的定义在/usr/include/bits/socket.h内。
返回值: 成功则返回socket处理代码,失败返回-1。
函数名: bind(对socket定位)
表头文件:#include
#include
定义函数:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
函数说明: bind()用来设置给参数sockfd的socket一个名称。此名称由参数my_addr指向一sockaddr结构,对于不同的socket domain定义了一个通用的数据结构
返回值: 成功则返回0,失败返回-1,错误原因存于errno中。
函数名: listen(等待连接)
表头文件:#include
定义函数:int listen(int s,int backlog);
函数说明: listen()用来等待参数s 的socket连线。参数backlog指定同时能处理的最大连接要求,如果连接数目达此上限则client端将收到ECONNREFUSED的错误。Listen()并未开始接收连线,只是设置socket为listen模式,真正接收client端连线的是accept()。通常listen()会在socket(),bind()之后调用,接着才调用accept()。
返回值: 成功则返回0,失败返回-1,错误原因存于errno。
附加说明: listen()只适用SOCK_STREAM或SOCK_SEQPACKET的socket类型。如果socket为AF_INET则参数backlog 最大值可设至128。
函数名: accept(接受socket连线)
表头文件:#include
#include
定义函数:int accept(int s,struct sockaddr * addr,int * addrlen);
函数说明: accept()用来接受参数s的socket连线。参数s的socket必需先经bind()、listen()函数处理过,当有连线进来时accept()会返回一个新的socket处理代码,往后的数据传送与读取就是经由新的socket处理,而原来参数s的socket能继续使用accept()来接受新的连线要求。连线成功时,参数addr所指的结构会被系统填入远程主机的地址数据,参数addrlen为scokaddr的结构长度。关于结构sockaddr的定义请参考bind()。
返回值: 成功则返回新的socket处理代码,失败返回-1,错误原因存于errno中。
函数名: send(经socket传送数据)
表头文件:#include
#include
定义函数:int send(int s,const void * msg,int len,unsigned int falgs);
函数说明: send()用来将数据由指定的socket 传给对方主机。参数s为已建立好连接的socket。参数msg指向欲连线的数据内容,参数len则为数据长度。参数flags一般设0。
返回值: 成功则返回实际传送出去的字符数,失败返回-1。错误原因存于errno
函数名: recv(经socket接收数据)
表头文件:#include
#include
定义函数:int recv(int s,void *buf,int len,unsigned int flags);
函数说明: recv()用来接收远端主机经指定的socket传来的数据,并把数据存到由参数buf 指向的内存空间,参数len为可接收数据的最大长度。
返回值: 接收的实际长度
函数名: connect(建立socket连线)
表头文件:#include
#include
定义函数:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
函数说明: connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址。结构sockaddr请参考bind()。参数addrlen为sockaddr的结构长度。
返回值: 成功则返回0,失败返回-1,错误原因存于errno中。
read : 由已打开的文件读取数据
write: 将数据写入已打开的文件内
htons:将16位主机字符顺序转换成网络字符顺序
bzero:将一段内存内容全清为零
inet_addr:将网络地址转成二进制的数字
函数可以在参考资料中的Linux 常用手册找到。
但为了代码的可兼容性,我个人的意见是,尽量少用依赖于平台的函数,多用标准库,这样代码可以轻易移植到其他支持 C++ 编译的平台。
参考资料:
Linux下 C++调用C 实现socket网络通讯编程 - 雕戈 - ITeye技术网站
sockaddr和sockaddr_in的区别 - The time is passing - ITeye技术网站
简单理解Socket - Samaritans - 博客园
Linux 常用C函数(中文版)