原创文章,可以任意转载,转载请注明出处:http://we029.com
环境:fedora 13 linux
编译器:linux g++(GCC) 4.4.4 20100630
一、基础篇
1.为什么是linux?
首先,linux不要钱,放心大胆的用,没有版权一说,很多标准支持很好,用来学习C++实在是再好不过了。
其次,windows下的socket其实也是基于伯克利的,和linux下的socket没有本质的区别,只是在较高层次上封装了一下并做了一些扩展。
更重要的是我实在搞不明白如何让.NET开发工具只安装我想要的东西,那玩意是在太臃肿了。
2.好了,废话少说,进入正题,一个基本的socket流程包括如下的部分:
常用的传输协议有两种类型有面向连接TCP协议和数据报协议UDP。
首先说下面向连接的协议,服务器端要建立一个socket,然后绑定(bind)到某个端口,然后监听(listen)该端口的连接,一旦有连接请求,就把这个请求放入连接队列,直到有accept()处理该连接,然后打开一个新的socket来处理现有连接,原socket仍在旧端口监听。然后服务器端可以接收客户端发来的数据或向客户端发送数据。完成之后关闭socket,释放为其分配的资源。
以下为详细过程:
1)服务器端:
1.1.1 首先创建一个socket文件描述符。
相关函数:
#include <sys/socket.h>
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
int socket (int __domain, int __type, int __protocol) __THROW;
__domain参数指定地址族,一般有PF_INET,对应ipV4,PF_INET6,对应ip version6.
__type指传输协议SOCK_STREAM对应TCP协议,SOCK_DGRAM协议,也可以使用SOCK_RAW等。
__protocol一般设为0,系统会自动根据前两个选择。
(在sys/socket.h中有详细定义)
返回值:正常情况下返回新建的socket的文件描述符(file descriptor),出错时返回-1.
eg.
int sockfd;
if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1)
{
perror(create socket failed!);
exit(1);
}
1.1.2然后bind()到某个端口:
bind(int sockfd,sockaddr * addr,socketlen_t addr_size);
sockfd为刚才创建的socket 文件描述符。
Addr是一个socketaddr结构体类型的指针,该结构体原型如下:
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
其中:
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
故:可以这么写:
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
第一个成员为TCP/IP地址族,常用的IPV4的我们就用AF_INET,
第二个成员用来占用一块内存其中包含IP地址和端口等信息。
在实际使用中可以用sockaddr_in结构体并转换的sockaddr类型。
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); //等于sa_family_t sin_family
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)];
};
sin_family是地址族,一般为AF_INET.
sin_port是端口号.注意这里要转换为网络高字节优先序可以用htons(ushort port_num)转换,如果是任意端口的话可以等于0,这样操作系统就会随机分配一个未使用的端口。
sin_addr结构体只有一个成员s_addr,是16进制的ip地址,如果是本机的话可以用INADDR_ANY常量。
sin_zero数组是一个内存占位成员,用于本结构体在大小上与sockaddr保持一致,一般通过memset使其用0填充。
bind的第三个参数是第二个参数的size,一般用sizeof()计算。
Eg:
struct sockaddr_in my_addr;
unsigned short int port=3990;
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(port);
my_addr.sin_addr.s_addr=INADDR_ANY;
memset(my_addr.sin_zero,0,8);
if(bind(sockfd,(struct sockaddr *) my_addr,sizeof(my_addr))==-1)
{
perror(“bind failed!”);
exit(1);
}
1.1.3现在该监听打开的端口了
listen(int sockfd,unsigned int con_num);
con_num是允许的最大排队连接数,超过这个数连接请求就会被拒绝。
如果listen出错,则返回-1
Eg
if(listen(sockfd,100)==-1)
{
perror(“listen failed!”);
exit(1);
}
好了,到这里,服务器端的前期准备工作就绪,可以通过accept()接收到达的连接请求。
1.1.4accept接收到达的请求:
accept(int sockfd,struct sockaddr * remote_addr,socklen_t * length_ptr)
sockfd是socket文件描述符
remote_addr指针用来保存返回的客户端的sockaddr结构
length_ptr指针用来保存返回的客户端sockaddr结构长度
通常将accept放入一个死循环中等待连接,直到有连接到达就处理
eg:
unsigned int client_len;
struct sockaddr_in remote_addr;
while(1)
{
if(accetp(sockfd,(struct sockaddr *)&remote_addr,&client_len)!=-1)
{
if(!fork())
{
printf(“a connection arrived!\n”);
exit(0);
}
}
continue;
}
以上的accept()工作模式为阻塞态,为了简单fork()一个子进程去处理发来的连接请求。更详细的将会在高级篇中说到。
好了 上一个完整的代码:
建立一个服务端程序,监听3990端口,传输协议为TCP协议,当有连接请求到来时向客户端发送一条欢迎信息并在控制台打印一条输出信息。
/*
* serv.cc
*
* Created on: 2010-12-6
* Author: ningxuan
*/
#include <iostream>
#include <cstdlib>
#include <sys/socket.h>
#include <cstring>
#include <cerrno>
#include <arpa/inet.h>
#include <cstdio>
#include <netinet/in.h>
using namespace std;
int main(int argc,char *argv[])
{
unsigned short int port=3990;
struct sockaddr_in my_addr, client_fd;
int sockfd,remote_sockfd;
char recv_buf[101],send_buf[100]=”welcome to the Server!you have connected in the server successed!”;
unsigned int client_len;
//client_len=sizeof(struct sockaddr_in);
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(port);//htons((uint16_t)port);
my_addr.sin_addr.s_addr=INADDR_ANY;
memset(&(my_addr.sin_zero),0,8);
if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1)
{
perror(“socket error!\n”);
exit(1);
}
cout<<“socket 建立成功!;”<<endl;
if(bind(sockfd,(sockaddr *)&my_addr,sizeof(my_addr))==-1)
{
perror(“bind error!”);
exit(1);
}
cout<<“bind 成功!”<<“端口:”<<ntohs(my_addr.sin_port)<<endl;
if(listen(sockfd,20)==-1)
{
perror(“listen failed:”);
}
cout<<“listen 成功!”<<endl;
while(1)
{
if((remote_sockfd=accept(sockfd,(struct sockaddr *)&client_fd,&client_len))!=-1)
{
if(!fork())
{
cout<<“has a new connection!”<<endl;
send(remote_sockfd,send_buf,100,0);
close(remote_sockfd);
exit(0);
}
}
continue;
}
close(sockfd);
return 0;
}
下面说一下send()和secv()
send()函数用于一个已连接的socket发送一条信息。
原型为:
send(int sockfd,void *buf,size_t size,int flags)
sockfd是已连接的socket文件描述服符。
Buf指向要发送的数据缓冲区。
Size是要发送的数据长度。
Flags一般填写0。
返回值为已发送的数据长度
recv()函数用来从一个已连接的socket中接收一条数据。
原型为:
recv(int sockfd,void * buffer,size_t size,int flags);
sockfd 为已连接的socket文件描述符
buffer指向一个用于接收信息的缓冲区。
size_t是缓冲区大小。
Flags一般写0。
返回值为已接收数据长度。
2)客户端
TCP模式客户端要和服务器通信西药如下几个步骤:
1.创建一个socket文件描述符
2.连接到远程的服务器。
3.发送和接收信息
4.关闭socket
1.1.1创建一个socket文件描述符:
int socket(int __domain,int __type,int __protocol)
如同服务器端,这里就不再详述。
返回值:返回一个文件描述符,出现错误返回-1
1.1.2连接到服务器:
int connect(int sockfd,struct sockaddr * addr,socklen_t size)
sockfd是socket的文件描述符
addr 是一个sockaddr结构体形的参数 ,可以使用sockaddr_in结构体代替。用于指示远程连接的地址和端口,以及地址族等信息,类似语服务器端。
注意:此结构体的地址是要16进制的格式,uint32_t inet_addr(const char *name)函数转换,相应的头文件为:arpa/inet.h
size是addr结构体的大小,直接用sizeof得到。
返回值:连接成功返回0,出错返回-1.
1.1.3接收发送信息
和服务端一样,采用send()和recv()函数发送和接收文件。
1.1.4关闭socket
close(int sockfd)函数用来关闭socket连接。
Eg:
/*
* client.cc
*
* Created on: 2010-12-6
* Author: ningxuan
*/
#include <iostream>
#include <sys/socket.h>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
int main()
{
struct sockaddr_in serv_addr;
int sockfd;
unsigned short int serv_port=3990;
char send_buf[100]=”hello there!”,recv_buf[100];
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(“127.0.0.1”);
serv_addr.sin_port=htons(serv_port);
memset(&(serv_addr.sin_zero),0,8);
if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1)
{
perror(“建立socket失败!”);
exit(1);
}
cout<<“connecting……”<<endl;
if(connect(sockfd,(sockaddr *)&serv_addr,sizeof(struct sockaddr_in))==-1)
{
perror(“connect failed!”);
exit(1);
}
cout<<“connect successed!”<<endl;
send(sockfd,send_buf,101,0);
while(1)
{
recv(sockfd,recv_buf,100,0);
cout<<recv_buf<<endl;
}
close(sockfd);
cout<<“connection disconnected!”<<endl;
return 0;
}