最近两天了linux的网络编程,自己写了一个demo,熟悉socket的几个主要API的用法:
虚拟机下的ubuntu 14.04 OS,程序包括:
客户端:发送连接请求;一旦连接建立,将用户输入的信息发送给服务器;输入end,客户端程序退出
服务器端:建立监听端口;接收连接请求;为每个请求创建一个线程,接收客户端的信息并打印。
#include
#include
#include
#include
#include
#include
#define DEBUG
#ifdef DEBUG
#define debug(x) printf(x)
#else
#define debug(x)
#endif
#define SERVER_PORT 1500
#define SERVER_IP "192.168.150.129"
#define LISTEN_NUM 10
typedef void (*sighandler_t)(int);
void sigint_handle(int sig); //sigint信号处理函数,Ctrl + c所发送的信号
void *func(void *arg); //线程处理函数
static int fd = -1;
int main(int argc , char * argv[])
{
int bind_ret = -1;
struct sockaddr_in sa_in = {0};
int listen_ret = -1;
int *accept_ret;
struct sockaddr_in client_sa = {0};
socklen_t client_len = 0;
pthread_t thread = -1;
int ret = -1;
sighandler_t sig_ret = (sighandler_t)-1;
sig_ret = signal(SIGINT,sigint_handle); //设置sigint信号的处理函数
if(SIG_ERR == sig_ret)
{
perror("signal");
exit(-1);
}
fd = socket(AF_INET,SOCK_STREAM,0); //建立一个监听socket,明确了socket的类型和类别:ipv4和tcp连接
if(fd < 0)
{
perror("soket");
exit(-1);
}
printf("fd:%d\n",fd);
sa_in.sin_family = AF_INET;
sa_in.sin_port = htons(SERVER_PORT); //将端口转为网络序,赋给结构体
sa_in.sin_addr.s_addr = inet_addr(SERVER_IP); //将点分十进制ip地址转换为二进制
bind_ret = bind(fd,(const struct sockaddr *)&sa_in,sizeof(sa_in)); //为监听描述符绑定ip和port,也被称为命名
if(bind_ret < 0)
{
perror("bind");
exit(-1);
}
debug("bind success\n");
listen_ret = listen(fd,LISTEN_NUM); //设置socket为监听状态
if(listen_ret < 0)
{
perror("listen");
exit(-1);
}
debug("listening\n");
while(1) //在循环中处理连接请求
{
accept_ret = (int *)malloc(sizeof(int)); //动态分配的原因:避免读写描述符被多个线程混用
*accept_ret = accept(fd,(struct sockaddr *)&client_sa,&client_len); //从链接队列中获取建立好的socket连接
if(accept_ret < 0)
{
perror("accept");
exit(-1);
}
printf("accept_ret,fd for client:%d\n",*accept_ret);
ret = pthread_create(&thread,NULL,func,(void *)accept_ret); //为连接创建线程
if(ret != 0)
{
perror("pthread_create");
exit(-1);
}
}
return 0;
}
void *func(void *fd) //线程处理函数
{
int *accept_ret = (int *)fd;
char receivebuf[20] = {0};
int recv_ret = -1;
while(1)
{
recv_ret = recv(*accept_ret,receivebuf,sizeof(receivebuf),0);//接收客户端数据
if(recv_ret < 0)
{
perror("recv");
exit(-1);
}
}
else if(recv_ret == 0) //连接断开
{
if(close(*accept_ret)) //关闭读取描述符
{
perror("close");
exit(-1);
}
printf("close:%d,exiting subthread\n",*accept_ret);
free(accept_ret); //释放动态分配的描述符空间
pthread_exit(NULL);//线程返回
}
printf("%d bytes from %d,%s\n",recv_ret,*accept_ret,receivebuf);
memset(receivebuf,0,strlen(receivebuf));
}
}
void sigint_handle(int sig) //信号处理函数
{
if(SIGINT != sig)return;
printf("\nserver termated\n");
close(fd);
exit(1);
}
#include
#include
#include
#include
#include
#include
#include
#define DEBUG
#ifdef DEBUG
#define debug(x) printf(x)
#else
#define debug(x)
#endif
#define SERVER_IP "192.168.150.129"
#define SERVER_PORT 1500
typedef void (*sighandler_t)(int);
void sigpipe_handle(int arg); //sigpipe信号处理函数,当send或是recv在等待发送或是接收数据时发现连接断开,系统会发出该信号
static int fd = -1;
int main(int argc,char *argv[])
{
struct sockaddr_in server_sa = {0};
int connect_ret = -1;
int send_ret = -1;
sighandler_t sig_ret = (sighandler_t)-1;
fd = socket(AF_INET,SOCK_STREAM,0); //建立socket连接,设置连接类型
if(fd < 0)
{
perror("socket");
exit(-1);
}
printf("fd:%d\n",fd);
sig_ret = signal(SIGPIPE,sigpipe_handle); //为sigpipe信号绑定处理函数
if(SIG_ERR == sig_ret)
{
perror("signal");
exit(-1);
}
server_sa.sin_port = htons(SERVER_PORT);
server_sa.sin_addr.s_addr = inet_addr(SERVER_IP);
server_sa.sin_family = AF_INET;
connect_ret = connect(fd,(struct sockaddr *)&server_sa,sizeof(server_sa)); //向服务器发送连接请求
if(connect_ret < 0)
{
perror("connect");
exit(-1);
}
debug("connect done\n");
char sendbuf[20] = {0};
while(1) //将用户输入的数据发送给服务器端,当输入end时,客户端退出
{
printf("input your data\n");
scanf("%s",sendbuf);
if(!strncmp(sendbuf, "end",3))
{
debug("exiting....\n");
close(fd);
return 0;
}
send_ret = send(fd,sendbuf,strlen(sendbuf),0); //向服务器发送信息
if(send_ret < 0)
{
perror("send");
exit(-1);
}
printf("data sent successfully:%d\n",send_ret);
}
}
void sigpipe_handle(int arg) //打印提示信息后再退出
{
if(SIGPIPE == arg)
{
printf("server disconnect\n");
close(fd);
debug("exiting\n");
exit(1);
}
}
这里只描述同步Socket的send函数的执行流程。当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据 copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
注意:
这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv则去检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:
根据上面的说明理解下面一段话:
发送方与接收方没有定制协议的情况下,接收方不可能知道发送方是否已经发送完毕,如果想要在这种情况下实现接收方接收发送方多次send()数据可以在recv时加个参数,让它只是看看有没有数据到达,recv最后那个参数设为MSG_OOB就可以,当发现有数据到时,你先Sleep一下,至于多长时间就要看实际情况,一般几十个毫秒就绝对够了,然后,你再recv一下,这次最后那个参数为MSG_WAITALL就可以,这样就一次性接收完毕
int listen(int socket, int backlog);
listen函数仅由TCP服务器调用,它做两件事情:
1、当socket函数创建一个套接口时,它被假设为一个主动套装口,也就是说,它是一个将调用connet发起连接的客户套接口。listen函数把一个未连接的套接口转换成一个被动套接口,指示内核应接受指向该套接口的连接请求。根据TCP状态转换图,调用listen导致套接口从CLOSED状态转换到LISTEN状态。
2、backlog其实是一个连接队列,以下是backlog队列大小公式。backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列