完整代码下载地址: http://download.csdn.net/detail/edroid1530/9669518
一、socket编程模型:
服务端工作流程:
- 调用 socket() 函数创建套接字
- 用 bind() 函数将创建的套接字与服务端IP地址绑定
- 用 listen() 函数监听socket() 函数创建的套接字,等待客户端连接
- 当客户端请求到来之后,调用 accept() 函数接受连接请求,返回一个对应于此连接的新的套接字,做好通信准备
- 调用 write()/read() 函数和 send()/recv() 函数进行数据的读写,通过 accept() 返回的套接字和客户端进行通信
- 关闭socket(close)
客户端工作流程:
- 调用 socket() 函数创建套接字
- 调用 connect() 函数连接服务端
- 调用write()/read() 函数或者 send()/recv() 函数进行数据的读写
- 关闭socket(close)
二、代码
服务端程序:
/*********************************************************************************
* Copyright: (C) 2016 SCUEC
* All rights reserved.
*
* Filename: server.c
* Description: This file is sever.c
*
* Version: 1.0.0(09/27/16)
* Author: LI WJNG
* ChangeLog: 1, Release initial version on "09/27/16 09:23:26"
*
********************************************************************************/
/* socket head file */
#include
#include
#include /* sockaddr_in() */
#include
/* unix standard head file */
#include
#include
#include
#include
#include /* strerror(),perror(), errno head file */
#include
#include
#define SERVERPORT 9999
#define MAX_BUFF 1024
#define LISTENQ 1024
/********************************************************************************
* Description:
* Input Args:
* Output Args:
* Return Value:
********************************************************************************/
int main (int argc, char **argv)
{
int sock_fd;
int acc_fd;
int retval; //return value of select()
int maxfd = -1;
unsigned int value = 1;
socklen_t len;
fd_set rfds; //descriptors
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
struct timeval tv; //waiting time
int server_port;
char buf[MAX_BUFF];
if(2 == argc)
{
server_port = atoi(argv[1]);
}
else
{
server_port = SERVERPORT;
}
/* use socket() */
if((sock_fd = socket(AF_INET, SOCK_STREAM, 0))<0)
{
printf("Socket failure!");
exit(1);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;/*IPV4*/
server_addr.sin_port = htons(server_port);/*绑定端口号*/
server_addr.sin_addr.s_addr = INADDR_ANY;/*监听所有网卡*/
/* use bind() function */
if(bind(sock_fd, (struct sockaddr *)&server_addr,sizeof(server_addr))<0)
{
printf("bind failure!");
exit(1);
}
/* use listen function */
if(listen(sock_fd,LISTENQ)<0)
{
printf("listen failure!");
exit(1);
}
/* Start connecting */
while(1)
{
printf("Waiting for Client to connecting... \n");
len = sizeof(struct sockaddr);
if((acc_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &len))<0)
{
perror("accept failure!");
exit(errno);
}
else
{
printf("server connected from:%s,por t%d, socked %d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),acc_fd);
}
/* Start chating */
while(1)
{
/* set empty */
FD_ZERO(&rfds);
/* add standard input handle */
FD_SET(0,&rfds);
maxfd = 0;
/* add the current connection handle acc_fd */
FD_SET(acc_fd,&rfds);
if(acc_fd>maxfd)
{
maxfd = acc_fd;
}
/* set maximun waiting time */
tv.tv_sec = 1;
tv.tv_usec = 0;
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval<0)
{
printf("select failure:%s\n",strerror(errno));
break;
}
else if(retval == 0)
{
continue;
}
else
{
if(FD_ISSET(acc_fd,&rfds))//Determine weather the state of the FD in the fdset collection is changing
{
/* if there is message,accept and display */
memset(&buf, 0, sizeof(buf));
/* recevie messages sent from client,up to MUX_BUFF bytes */
len = recv(acc_fd, buf, MAX_BUFF, 0);
if(len > 0)
{
printf("Recevie messages success:%s\n",buf);
}
else
{
if(len<0)
{
printf("Recevie messages failure,errno is:%s",errno,strerror(errno));
}
else
printf("quit\n");
break;
}
}//end of if(FD_ISSET(acc_fd,&rfds))
if(FD_ISSET(0,&rfds))
{
memset(&buf, 0,sizeof(buf));
read(0,buf,MAX_BUFF);
len = send(acc_fd,buf,strlen(buf)-1,0);
if(len<0)
{
printf("error");
break;
}
else
{
printf("messages success:\n %s",buf);
}
}//FD_ISSET = 0
}
}//end of second while()
close(acc_fd);
}
close(sock_fd);
return 0;
} /* ----- End of main() ----- */
客户端程序:
/*********************************************************************************
* Copyright: (C) 2016 SCUEC
* All rights reserved.
*
* Filename: client.c
* Description: This file is client.c
*
* Version: 1.0.0(09/27/2016)
* Author: LI WJNG
* ChangeLog: 1, Release initial version on "09/27/2016 07:34:31 PM"
*
********************************************************************************/
/* Socket head file */
#include
#include
#include/*sockaddr_in{}*/
#include
/* Unix standard head file */
#include
#include
#include
#include
#include /* strerror(),perror(),errno head file*/
#include
#include
#define MAXBUFF 1024
#define PORT 9999
/********************************************************************************
* Description:
* Input Args:
* Output Args:
* Return Value:
********************************************************************************/
int main (int argc, char **argv)
{
int sock_fd;
int len;
int retval;
int maxfd = -1;
fd_set rfds;
struct sockaddr_in dest;
struct timeval tv;
char buff[MAXBUFF];
if(2 != argc)
{
printf("Plaese input IP adress\n");
return -1;
}
/* Use Socket() function */
if((sock_fd = socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("Socket failure\n");
exit(errno);
}
/*Initialize the address and port information of the server */
memset(&dest,0,sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(PORT);
if(inet_pton(AF_INET,argv[1],&dest.sin_addr)<=0)
{
perror("inet_pton() to set IP address failure");
exit(1);
}
/* connect to server */
printf("Connect to server[%s:%d]\n",argv[1],PORT);
if(connect(sock_fd,(struct sockaddr *)&dest,sizeof(dest))<0)
{
perror("connect error");
exit(errno);
}
printf("\nReady to chatting...\n");
while(1)
{
/* Set empty */
FD_ZERO(&rfds);
/* Add the standard input handle 0 */
FD_SET(0,&rfds);
maxfd = 0;
/* Add the current connection handle new_fd */
FD_SET(sock_fd,&rfds);
if(sock_fd > maxfd)
{
maxfd = sock_fd;
}
/* Set maximum waiting time */
tv.tv_sec = 1;
tv.tv_usec = 0;
/* Start to wait */
retval = select(maxfd + 1,&rfds,NULL,NULL,&tv);
if(retval < 0)
{
printf("select error\n");
break;
}
else if(retval == 0)
{
continue;
}
else
{
if(FD_ISSET(sock_fd,&rfds))/* Determine whether the state of the FD in the fdset collection is changing. */
{
memset(&buff,0,sizeof(buff));
/* Receive messages sent from the other, up to MAX_BUFF bytes */
len = recv(sock_fd,buff,MAXBUFF,0);
if(len > 0)
{
printf("Receive messages success: %s \n",buff);
}
else
{
if(len < 0)
{
printf("Receive messages fail,error is:%s.\n",errno,strerror(errno));
}
else
printf("quit\n");
break;
}
}//FD_ISSET = sock_fd
if(FD_ISSET(0,&rfds))
{
memset(&buff,0,sizeof(buff));
read(0,buff,MAXBUFF);
len = send(sock_fd,buff,strlen(buff)-1,0);
if(len < 0)
{
printf("Send error\n");
break;
}
else
{
printf("Send messages success!\n %s...\n",buff);
}
}//FD_ISSET = 0
}//select finished
}//End of while
close(sock_fd);
return 0;
} /* ----- End of main() ----- */
相关函数介绍及用法:
1.socket()函数:创建一个套接字
比喻:
小明家里安装固定电话,第一步首先得去买个电话机,才能完成后续通信。因此,在网络编程前,我们需要调用socket创建个套接字(类似购买个电话机)。
#include
/*
*@parm domain:告诉系统使用哪个协议族(IPV4 or IPV6)
*@parm type :指定服务类型
*@parm protocol:一般设置为0
*@return 函数执行成功返回一个文件描述符(大于0),失败返回-1
*/
int socket(int domain, int type, int protocol);//创建套接字,即返回 socket 文件描述符
domain: AF_INET、AF_INET6、AF_UNIX、AF_UPSPEC
type: SOCK_DGRAM、 SOCK_RAW、 SOCK_SEQPACKET、 SOCK_STREAM
protocol: IPPROTO_IP、IPPROTO_IPV6、IPPROTO_ICMP、IPPROTO_RAW、 IPPROTO_TCP、 IPPROTO_UDP
2.bind()函数:将套接字与制定端口相连。
当调用socket函数创建套接字后,该套接字并没有与本机地址和端口等信息相连,bind函数将完成这些工作。bind函数中的sockfd参数为调用socket函数后返回的文件描述符。my_addr参数为指向sockaddr结构体的指针(该结构体 中保存有端口和IP地址信息)。addlen参数为结构体sockaddr的长度。
比喻:我们创建了socket(安装电话机)以后,需要进行socket命名(应该去中国联通申请一个电话号码,并将该号码和电话机进行绑定)。
#include
/*
*@param sockfd:套接字(socket()返回的文件描述符)
*@parm server_addr:要绑定的端口和IP
*@parm addr_len:server_addr 的长度
*@return 函数执行成功返回1,失败返回-1*/
int bind(int sockfd, const struct sockaddr *server_addr, socklen_t addrlen)//将端口信息与套接字绑定
错误信息:
EACCES:地址受到保护,用户非超级用户
EADDRINUSE:指定的地址已经在使用。
EBADF:sockfd参数为非法的文件描述符。
EINVAL:socket已经和地址绑定。
ENOTSOCK:参数sockfd为文件描述符。
3.listen()函数:监听socket
listent函数创建一个
监听队列
以存放待处理的客户连接,将套接字sockfd指定为被监听的socket(类似指定电话机处于可接受的状态)。其中backlog一般取值为5。
#include
/*
*@para sockfd :套接字(socket()函数返回的文件描述符)
*@para backlog :提示内核监听最大长度
*@return 函数执行成功返回1,失败返回-1
*/
int listen(int sockfd, int backlog);
4.accept()函数:函数执行成功返回个新的文件描述符new_fd,称之为
已连接套接字,后续可通过
已连接套接字进行通信
比喻:
当电话响了,小明需要拿起电话机接受对方的连接请求,才能完成通话。网络通信原理相似,当有网络连接请求,需要调用相关函数接受连接,进行处理。
#include
#include
/*
*@para sockfd:listen()函数 指定的socket()函数 返回的文件描述符)
*@para addr :请求连接方(客户端)的IP,通常设为NULl,表示任何客户端都能联
*@para addrlen : 客户端地址长度
*@return 函数执行成功返回新的文件描述符new_fd,失败返回-1
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.connect()函数:
当服务端做好连接准备等待客户端链接时,客户端调用 connect()函数连接服务端、
#include
#include
/**
* 建立连接
*
*@param sockfd:套接字(socket()返回的文件描述符)
* @param server_addr 服务端地址
* @param addrlen 服务端地址地址长度
* @return 函数执行成功返回0,失败返回-1
*/
int connect(int sockfd, const struct sockaddr *server_addr, socklen_t *addrlen);
6.select()函数:等待文件描述符状态的改变,
#include <>
/*
*@para maxfdp: 最大的文件描述符+1;
*@para *readfds: 是否有文件可读,若有 返回1,无 返回0
*@para *writefds: 是否有文件可写 若有返回1,无 返回0
*@para *errorfds: 监视文件错误异常情况
*@para *timeout: select()等待时间
@return select()错误返回-1;某些文件可读写或出错返回正值;等待超时,没有可读写或错误的文件返回0
*/
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
struct fd_set 是文件描述符集,顾名思义,这个结构体里面存放的是文件描述符的集合及文件句柄,fd_set 集合可通过一些宏由人来操作
FD_ZERO(fd_set *) :
清空集合
;
FD_SET(int ,fd_set*) :
将一个给定的文件描述符加入集合之中
;
FD_CLR(int,fd_set*) :
将一个给定的文件描述符从集合中删除
;
FD_ISSET(int ,fd_set* ) :
检查集合中指定的文件描述符是否可以读写
。
struct timeval 用来代表时间值,其中有两个成员秒数和毫秒数
struct timeval
{
time_tv tv_sec;
time_tv tv_usec;
}
在网上看到几篇关于网络socket编程相关函数的分析,写的特别通俗易懂。
socket函数分析:http://blog.csdn.net/stpeace/article/details/13279785
socketaddr_in结构体:http://blog.csdn.net/stpeace/article/details/13400115
bind函数分析:http://blog.csdn.net/stpeace/article/details/13410839
listen函数分析:http://blog.csdn.net/stpeace/article/details/13416891
accept函数分析:http://blog.csdn.net/stpeace/article/details/13424223
遇到的问题及解决方案:
相信不少新手在写程序的时候都会犯下及其低级的错误,但是小错误往往消耗大量的时间。比如本人在编写这个程序的时候就在不该加分号的地方加上了分号又或者在多重调用的时候括号的位置放错地方,导致错误很长时间都没有找到,浪费了大量的时间。