嵌入式linux TCP socket编程
2017-03-08 17:19
316 查看
一、利用内核编程的API
sendto 和 recvfrom
sendto()_Linux
C函数
sendto(经socket传送数据)
send , sendmsg,recv , recvfrom , socket
#include < sys/types.h >
#include < sys/socket.h >
int sendto ( socket s , const void * msg, int len, unsigned int flags, const
struct sockaddr * to , int tolen ) ;
sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。参数tolen为sockaddr的结构长度。
返回值:成功则返回接收到的字符数,失败返回-1.
EBADF 参数s非法的socket处理代码。
EFAULT 参数中有一指针指向无法存取的内存空间。
WNOTSOCK 参数 s为一文件描述词,非socket。
EINTR 被信号所中断。
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断的。
ENOBUFS 系统的缓冲内存不足。
EINVAL 传给系统调用的参数不正确。
ssize_t recvfrom(int sockfd,void *buf,int len,unsigned int flags,
struct sockaddr *from,socket_t *fromlen);
recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
返回值:成功则返回接收到的字符数,失败返回-1.
EBADF 参数s非合法的socket处理代码
EFAULT 参数中有一指针指向无法存取的内存空间。
ENOTSOCK 参数s为一文件描述词,非socket。
EINTR 被信号所中断。
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断。
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确。
#include < sys/types.h >
#include < sys/socket.h >
#include <arpa.inet.h>
#define PORT 2345 /*使用的port*/
main(){
int sockfd,len;
struct sockaddr_in addr;
char buffer[256];
/*建立socket*/
if(sockfd=socket (AF_INET,SOCK_DGRAM,0))<0){
perror (“socket”);
exit(1);
}
/*填写sockaddr_in 结构*/
addr.sin_port=htons(PORT);
addr.sin_addr=hton1(INADDR_ANY)
;
if (bind(sockfd, &addr, sizeof(addr))<0){
perror(“connect”);
exit(1);
}
while(1){
bzero(buffer,sizeof(buffer));
len = recvfrom(socket,buffer,sizeof(buffer), 0 , &addr &addr_len);
/*显示client端的网络地址*/
printf(“receive from %s\n “ , inet_ntoa( addr.sin_addr));
/*将字串返回给client端*/
sendto(sockfd,buffer,len,0,&addr,addr_len);”
}
}
二、利用socket文件描述符
write/read
TCPServer端
[cpp] view
plain copy
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory.h>
#include <unistd.h>
//#include <linux/in.h>
#include <netinet/in.h>
//#include <linux/inet_diag.h>
#include <arpa/inet.h>
#include <signal.h>
/**
关于 sockaddr sockaddr_in socketaddr_un说明
http://maomaozaoyue.blog.sohu.com/197538359.html
*/
#define PORT 11910 //定义通信端口
#define BACKLOG 5 //定义侦听队列长度
#define buflen 1024
void process_conn_server(int s);
void sig_pipe(int signo);
int ss,sc; //ss为服务器socket描述符,sc为某一客户端通信socket描述符
int main(int argc,char *argv[])
{
struct sockaddr_in server_addr; //存储服务器端socket地址结构
struct sockaddr_in client_addr; //存储客户端 socket地址结构
int err; //返回值
pid_t pid; //分叉进行的ID
/*****************socket()***************/
ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流
if(ss<0)
{
printf("server : server socket create error\n");
return -1;
}
//注册信号
sighandler_t ret;
ret = signal(SIGTSTP,sig_pipe);
if(SIG_ERR == ret)
{
printf("信号挂接失败\n");
return -1;
}
else
printf("信号挂接成功\n");
/******************bind()****************/
//初始化地址结构
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET; //协议族
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
server_addr.sin_port = htons(PORT);
err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));
if(err<0)
{
printf("server : bind error\n");
return -1;
}
/*****************listen()***************/
err = listen(ss,BACKLOG); //设置监听的队列大小
if(err < 0)
{
printf("server : listen error\n");
return -1;
}
/****************accept()***************/
/**
为类方便处理,我们使用两个进程分别管理两个处理:
1,服务器监听新的连接请求;2,以建立连接的C/S实现通信
这两个任务分别放在两个进程中处理,为了防止失误操作
在一个进程中关闭 侦听套接字描述符 另一进程中关闭
客户端连接套接字描述符。注只有当所有套接字全都关闭时
当前连接才能关闭,fork调用的时候父进程与子进程有相同的
套接字,总共两套,两套都关闭掉才能关闭这个套接字
*/
for(;;)
{
socklen_t addrlen = sizeof(client_addr);
//accept返回客户端套接字描述符
sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此处为了获取返回值使用 指针做参数
if(sc < 0) //出错
{
continue; //结束此次循环
}
else
{
printf("server : connected\n");
}
//创建一个子线程,用于与客户端通信
pid = fork();
//fork 调用说明:子进程返回 0 ;父进程返回子进程 ID
if(pid == 0) //子进程,与客户端通信
{
close(ss);
process_conn_server(sc);
}
else
{
close(sc);
}
}
}
/**
服务器对客户端连接处理过程;先读取从客户端发送来的数据,
然后将接收到的数据的字节的个数发送到客户端
*/
//通过套接字 s 与客户端进行通信
void process_conn_server(int s)
{
ssize_t size = 0;
char buffer[buflen]; //定义数据缓冲区
for(;;)
{
//等待读
for(size = 0;size == 0 ;size = read(s,buffer,buflen));
//输出从客户端接收到的数据
printf("%s",buffer);
//结束处理
if(strcmp(buffer,"quit") == 0)
{
close(s); //成功返回0,失败返回-1
return ;
}
sprintf(buffer,"%d bytes altogether\n",size);
write(s,buffer,strlen(buffer)+1);
}
}
void sig_pipe(int signo)
{
printf("catch a signal\n");
if(signo == SIGTSTP)
{
printf("接收到 SIGTSTP 信号\n");
int ret1 = close(ss);
int ret2 = close(sc);
int ret = ret1>ret2?ret1:ret2;
if(ret == 0)
printf("成功 : 关闭套接字\n");
else if(ret ==-1 )
printf("失败 : 未关闭套接字\n");
exit(1);
}
}
TCPClient端
[cpp] view
plain copy
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h> //添加信号处理 防止向已断开的连接通信
/**
信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号,
信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认
响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行
*/
#define PORT 11910
#define Buflen 1024
void process_conn_client(int s);
void sig_pipe(int signo); //用户注册的信号函数,接收的是信号值
int s; //全局变量 , 存储套接字描述符
int main(int argc,char *argv[])
{
struct sockaddr_in server_addr;
int err;
sighandler_t ret;
char server_ip[50] = "";
/********************socket()*********************/
s= socket(AF_INET,SOCK_STREAM,0);
if(s<0)
{
printf("client : create socket error\n");
return 1;
}
//信号处理函数 SIGINT 是当用户按一个 Ctrl-C 建时发送的信号
ret = signal(SIGTSTP,sig_pipe);
if(SIG_ERR == ret)
{
printf("信号挂接失败\n");
return -1;
}
else
printf("信号挂接成功\n") ;
/*******************connect()*********************/
//设置服务器地址结构,准备连接到服务器
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*将用户数入对额字符串类型的IP格式转化为整型数据*/
//inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);
printf("please input server ip address : \n");
read(0,server_ip,50);
//err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
err = connect(s,(struct sockaddr *)&server_addr,sizeof(struct
sockaddr));
if(err == 0)
{
printf("client : connect to server\n");
}
else
{
printf("client : connect error\n");
return -1;
}
//与服务器端进行通信
process_conn_client(s);
close(s);
}
void process_conn_client(int s)
{
ssize_t size = 0;
char buffer[Buflen];
for(;;)
{
memset(buffer,'\0',Buflen);
/*从标准输入中读取数据放到缓冲区buffer中*/
size = read(0,buffer,Buflen); // 0,被默认的分配到标准输入 1,标准输出 2,error
if(size > 0)
{
//当向服务器发送 “quit” 命令时,服务器首先断开连接
write(s,buffer,strlen(buffer)+1); //向服务器端写
//等待读取到数据
for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );
write(1,buffer,strlen(buffer)+1); //向标准输出写
}
}
}
void sig_pipe(int signo) //传入套接字描述符
{
printf("Catch a signal\n");
if(signo == SIGTSTP)
{
printf("接收到 SIGTSTP 信号\n");
int ret = close(s);
if(ret == 0)
printf("成功 : 关闭套接字\n");
else if(ret ==-1 )
printf("失败 : 未关闭套接字\n");
exit(1);
}
}
注意这里 的
sighandler_t ret;
在不同的编译环境中可能会有错误的
所以编译选项要加上
-D_GNU_SOURCE
三、基于I/O
多路复用技术的并发TCP
在实际的应用中, 要求一个服务器能同时处理大量的客户请求, 所有这些客户将访问绑
定在某一个特定套接字地址上的服务器。 因此, 服务器必须满足并发的需求。 如果不采用并
发技术, 当服务器处理一个客户请求时, 会拒绝其他客户端请求, 造成其他客户要不断的请
求并长期等待。
在 Linux( Unix) 系统中并发服务器有三种设计方式:
( 1) 多进程
进程是执行中的计算机程序, 可以认为是一个程序的一次运行。 它是一个动态的实体,
是独立的任务。 每个单独的进程运行在自己的虚拟地址空间中, 并且它只能通过安全的内核
管理机制和其它进程交互。 若是一个进程崩溃不会引起其它进程崩溃。
在 Linux(Unix)系统中, 多个进程可以同时执行相同的代码, 从而支持并发。
对于单个 CPU 系统而言,
CPU 一次只能执行一个进程, 但操作系统可通过分时处理,
每个进程在每个时间段中执行, 因此对于用户而言, 这些进程在同时执行。
( 2) 多线程
线程与进程类似, 也支持并发执行。 与进程不同的一点, 在同一进程中所有线程共享
相同的全程变量以及系统分配给进程的资源。 因此, 线程占用较少的系统资源, 并且线程之
间切换更快。
( 3)I/O
多路复用( select
和 poll函数)
另一种支持并发的方法是 I/O多路复用。
select()函数是系统提供的, 它可以在多个描
述符中选择被激活的描述符进行操作。
例如: 一个进程中有多个客户连接, 即存在多个 TCP套接字描述符。
select()函数阻塞
直到任何一个描述符被激活, 即有数据传输。 从而避免了进程为等待一个已连接上的数据而
无法处理其他连接。 因而, 这是一个时分复用的方法, 从用户角度而言, 它实现了一个进程
或线程中的并发处理。
I/O 多路复用技术的最大优势是系统开销小, 系统不必创建进程、 线程, 也不必维护这
些进程/线程, 从而大大减少了系统的开销。
select()函数用于实现I/O
多路复用, 它允许进程指示系统内核等待多个事件中的任何一
个发生, 并仅在一个或多个事情发送或经过某指定的时间后才唤醒进程。
它的原型如下,
#include<sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set * errorfds, struct timeval *timeout);
ndfs: select() 函数监视描述符数的最大值。 根据进程中打开的描述符数而定, 一般设为要
监视的描述符的最大数加 1。
readfds: select() 函数监视的可读描述符集合。
writefds: select()函数监视的可写描述符集合。
errorfds: select()函数监视的异常描述符集合。
timeout: select()函数超时结束时间
返回值。 如果成功返回总的位数, 这些位对应已准备好的描述符。 否则返回-1, 并在errno
中设置相应的错误码。
FD_ZERO(fd_set *fdset): 清空fdset
与所有描述符的联系
FD_SET(int fd, fd_set *fdset): 建立描述符fd
与 fdset的联系
FD_CLR(int fd, fd_set *fdset): 撤销描述符fd
与 fdset的联系
FD_ISSET(int fd,fd_set *fdset) :: 检查与fdset
联系的描述符 fd
是否可读写, 返回非 0表示可读写。
采用 select()函数实现I/O
多路复用的基本步骤如下:
( 1) 清空描述符集合
( 2) 建立需要监视的描述符与描述符集合的联系
( 3) 调用 select()函数
( 4) 检查所有需要监视的描述符, 利用 FD_ISSET 判断是否准备好
( 5) 对已准备好的描述符进行 I/O 操作
下面是在 eHome 中使用的一个select
函数实例。
// name : Ehome_server.c
// author : pyy
// date : 2008-3-5
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 1234 //使用的 port号码
#define MAXSOCKFD 10 //可同时服务的最大连接数目
int main()
{
int sockfd,newsockfd,is_connected[MAXSOCKFD],fd;
struct sockaddr_in addr;
int addr_len = sizeof(struct sockaddr_in);
fd_set readfds;
char buffer[256];
int length;
char buf2[256]; //add
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{ perror("socket"); exit(1);}
//填写 sockaddr_in
结构
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr))<0)
{ perror("bind"); exit(1);}
if(listen(sockfd,3)<0)
{ perror("listen"); exit(1);}
//清楚连线状态的旗标
for(fd=0;fd<MAXSOCKFD;fd++)
is_connected[fd] = 0;
while(1)
{
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
for(fd=0; fd<MAXSOCKFD; fd++)
if(is_connected[fd]) FD_SET(fd,&readfds);
if(!select(MAXSOCKFD,&readfds,NULL,NULL,NULL)) continue;
//判断是否有新的连线或新信息进来
for(fd=0; fd<MAXSOCKFD; fd++)
if(FD_ISSET(fd,&readfds))
{
if(sockfd == fd)
{
//接收新连线
if((newsockfd= accept(sockfd,(struct sockaddr *)&addr,&addr_len))<0)
perror("accept");
//将欢迎字符串送给 client端
is_connected[newsockfd] = 1;
printf("Connect from %s\n",inet_ntoa(addr.sin_addr));
}
else
{
//接收新信息
bzero(buffer,sizeof(buffer));
if( ( length=read(fd,buffer,sizeof(buffer)) ) <=0)
{
//连线已中断, 清除连线状态旗标
printf("Connection closed.\n");
is_connected[fd] = 0;
close(fd);
}
else
{
printf("Receive message: %s\n",buffer);
write(fd,buffer,length);
bzero(buf2,sizeof(buf2));
sprintf(buf2,"%5.3f%5.3f%5.3f%5.2f%5.3f%5.2f",1.305,2.226,3.333,20.56,9.0,28.5);
length= write(fd,buf2,strlen(buf2));
printf("Send message: %s length= %d\n",buf2,length);
}
}
}
}
}
sendto 和 recvfrom
sendto()_Linux
C函数
sendto(经socket传送数据)
相关函数
send , sendmsg,recv , recvfrom , socket
表头文件
#include < sys/types.h >#include < sys/socket.h >
定义函数
int sendto ( socket s , const void * msg, int len, unsigned int flags, conststruct sockaddr * to , int tolen ) ;
函数说明
sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。参数tolen为sockaddr的结构长度。
返回值
返回值:成功则返回接收到的字符数,失败返回-1.
错误代码
EBADF 参数s非法的socket处理代码。EFAULT 参数中有一指针指向无法存取的内存空间。
WNOTSOCK 参数 s为一文件描述词,非socket。
EINTR 被信号所中断。
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断的。
ENOBUFS 系统的缓冲内存不足。
EINVAL 传给系统调用的参数不正确。
定义函数
ssize_t recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,socket_t *fromlen);
函数说明
recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
返回值
返回值:成功则返回接收到的字符数,失败返回-1.
错误代码
EBADF 参数s非合法的socket处理代码EFAULT 参数中有一指针指向无法存取的内存空间。
ENOTSOCK 参数s为一文件描述词,非socket。
EINTR 被信号所中断。
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断。
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确。
范例
#include < sys/types.h >#include < sys/socket.h >
#include <arpa.inet.h>
#define PORT 2345 /*使用的port*/
main(){
int sockfd,len;
struct sockaddr_in addr;
char buffer[256];
/*建立socket*/
if(sockfd=socket (AF_INET,SOCK_DGRAM,0))<0){
perror (“socket”);
exit(1);
}
/*填写sockaddr_in 结构*/
bzero ( &addr, sizeof(addr) );
addr.sin_family=AF_INET;addr.sin_port=htons(PORT);
addr.sin_addr=hton1(INADDR_ANY)
;
if (bind(sockfd, &addr, sizeof(addr))<0){
perror(“connect”);
exit(1);
}
while(1){
bzero(buffer,sizeof(buffer));
len = recvfrom(socket,buffer,sizeof(buffer), 0 , &addr &addr_len);
/*显示client端的网络地址*/
printf(“receive from %s\n “ , inet_ntoa( addr.sin_addr));
/*将字串返回给client端*/
sendto(sockfd,buffer,len,0,&addr,addr_len);”
}
}
二、利用socket文件描述符
write/read
TCPServer端
[cpp] view
plain copy
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory.h>
#include <unistd.h>
//#include <linux/in.h>
#include <netinet/in.h>
//#include <linux/inet_diag.h>
#include <arpa/inet.h>
#include <signal.h>
/**
关于 sockaddr sockaddr_in socketaddr_un说明
http://maomaozaoyue.blog.sohu.com/197538359.html
*/
#define PORT 11910 //定义通信端口
#define BACKLOG 5 //定义侦听队列长度
#define buflen 1024
void process_conn_server(int s);
void sig_pipe(int signo);
int ss,sc; //ss为服务器socket描述符,sc为某一客户端通信socket描述符
int main(int argc,char *argv[])
{
struct sockaddr_in server_addr; //存储服务器端socket地址结构
struct sockaddr_in client_addr; //存储客户端 socket地址结构
int err; //返回值
pid_t pid; //分叉进行的ID
/*****************socket()***************/
ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流
if(ss<0)
{
printf("server : server socket create error\n");
return -1;
}
//注册信号
sighandler_t ret;
ret = signal(SIGTSTP,sig_pipe);
if(SIG_ERR == ret)
{
printf("信号挂接失败\n");
return -1;
}
else
printf("信号挂接成功\n");
/******************bind()****************/
//初始化地址结构
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET; //协议族
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
server_addr.sin_port = htons(PORT);
err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));
if(err<0)
{
printf("server : bind error\n");
return -1;
}
/*****************listen()***************/
err = listen(ss,BACKLOG); //设置监听的队列大小
if(err < 0)
{
printf("server : listen error\n");
return -1;
}
/****************accept()***************/
/**
为类方便处理,我们使用两个进程分别管理两个处理:
1,服务器监听新的连接请求;2,以建立连接的C/S实现通信
这两个任务分别放在两个进程中处理,为了防止失误操作
在一个进程中关闭 侦听套接字描述符 另一进程中关闭
客户端连接套接字描述符。注只有当所有套接字全都关闭时
当前连接才能关闭,fork调用的时候父进程与子进程有相同的
套接字,总共两套,两套都关闭掉才能关闭这个套接字
*/
for(;;)
{
socklen_t addrlen = sizeof(client_addr);
//accept返回客户端套接字描述符
sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此处为了获取返回值使用 指针做参数
if(sc < 0) //出错
{
continue; //结束此次循环
}
else
{
printf("server : connected\n");
}
//创建一个子线程,用于与客户端通信
pid = fork();
//fork 调用说明:子进程返回 0 ;父进程返回子进程 ID
if(pid == 0) //子进程,与客户端通信
{
close(ss);
process_conn_server(sc);
}
else
{
close(sc);
}
}
}
/**
服务器对客户端连接处理过程;先读取从客户端发送来的数据,
然后将接收到的数据的字节的个数发送到客户端
*/
//通过套接字 s 与客户端进行通信
void process_conn_server(int s)
{
ssize_t size = 0;
char buffer[buflen]; //定义数据缓冲区
for(;;)
{
//等待读
for(size = 0;size == 0 ;size = read(s,buffer,buflen));
//输出从客户端接收到的数据
printf("%s",buffer);
//结束处理
if(strcmp(buffer,"quit") == 0)
{
close(s); //成功返回0,失败返回-1
return ;
}
sprintf(buffer,"%d bytes altogether\n",size);
write(s,buffer,strlen(buffer)+1);
}
}
void sig_pipe(int signo)
{
printf("catch a signal\n");
if(signo == SIGTSTP)
{
printf("接收到 SIGTSTP 信号\n");
int ret1 = close(ss);
int ret2 = close(sc);
int ret = ret1>ret2?ret1:ret2;
if(ret == 0)
printf("成功 : 关闭套接字\n");
else if(ret ==-1 )
printf("失败 : 未关闭套接字\n");
exit(1);
}
}
TCPClient端
[cpp] view
plain copy
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h> //添加信号处理 防止向已断开的连接通信
/**
信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号,
信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认
响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行
*/
#define PORT 11910
#define Buflen 1024
void process_conn_client(int s);
void sig_pipe(int signo); //用户注册的信号函数,接收的是信号值
int s; //全局变量 , 存储套接字描述符
int main(int argc,char *argv[])
{
struct sockaddr_in server_addr;
int err;
sighandler_t ret;
char server_ip[50] = "";
/********************socket()*********************/
s= socket(AF_INET,SOCK_STREAM,0);
if(s<0)
{
printf("client : create socket error\n");
return 1;
}
//信号处理函数 SIGINT 是当用户按一个 Ctrl-C 建时发送的信号
ret = signal(SIGTSTP,sig_pipe);
if(SIG_ERR == ret)
{
printf("信号挂接失败\n");
return -1;
}
else
printf("信号挂接成功\n") ;
/*******************connect()*********************/
//设置服务器地址结构,准备连接到服务器
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*将用户数入对额字符串类型的IP格式转化为整型数据*/
//inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);
printf("please input server ip address : \n");
read(0,server_ip,50);
//err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
err = connect(s,(struct sockaddr *)&server_addr,sizeof(struct
sockaddr));
if(err == 0)
{
printf("client : connect to server\n");
}
else
{
printf("client : connect error\n");
return -1;
}
//与服务器端进行通信
process_conn_client(s);
close(s);
}
void process_conn_client(int s)
{
ssize_t size = 0;
char buffer[Buflen];
for(;;)
{
memset(buffer,'\0',Buflen);
/*从标准输入中读取数据放到缓冲区buffer中*/
size = read(0,buffer,Buflen); // 0,被默认的分配到标准输入 1,标准输出 2,error
if(size > 0)
{
//当向服务器发送 “quit” 命令时,服务器首先断开连接
write(s,buffer,strlen(buffer)+1); //向服务器端写
//等待读取到数据
for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );
write(1,buffer,strlen(buffer)+1); //向标准输出写
}
}
}
void sig_pipe(int signo) //传入套接字描述符
{
printf("Catch a signal\n");
if(signo == SIGTSTP)
{
printf("接收到 SIGTSTP 信号\n");
int ret = close(s);
if(ret == 0)
printf("成功 : 关闭套接字\n");
else if(ret ==-1 )
printf("失败 : 未关闭套接字\n");
exit(1);
}
}
注意这里 的
sighandler_t ret;
在不同的编译环境中可能会有错误的
所以编译选项要加上
-D_GNU_SOURCE
三、基于I/O
多路复用技术的并发TCP
在实际的应用中, 要求一个服务器能同时处理大量的客户请求, 所有这些客户将访问绑
定在某一个特定套接字地址上的服务器。 因此, 服务器必须满足并发的需求。 如果不采用并
发技术, 当服务器处理一个客户请求时, 会拒绝其他客户端请求, 造成其他客户要不断的请
求并长期等待。
在 Linux( Unix) 系统中并发服务器有三种设计方式:
( 1) 多进程
进程是执行中的计算机程序, 可以认为是一个程序的一次运行。 它是一个动态的实体,
是独立的任务。 每个单独的进程运行在自己的虚拟地址空间中, 并且它只能通过安全的内核
管理机制和其它进程交互。 若是一个进程崩溃不会引起其它进程崩溃。
在 Linux(Unix)系统中, 多个进程可以同时执行相同的代码, 从而支持并发。
对于单个 CPU 系统而言,
CPU 一次只能执行一个进程, 但操作系统可通过分时处理,
每个进程在每个时间段中执行, 因此对于用户而言, 这些进程在同时执行。
( 2) 多线程
线程与进程类似, 也支持并发执行。 与进程不同的一点, 在同一进程中所有线程共享
相同的全程变量以及系统分配给进程的资源。 因此, 线程占用较少的系统资源, 并且线程之
间切换更快。
( 3)I/O
多路复用( select
和 poll函数)
另一种支持并发的方法是 I/O多路复用。
select()函数是系统提供的, 它可以在多个描
述符中选择被激活的描述符进行操作。
例如: 一个进程中有多个客户连接, 即存在多个 TCP套接字描述符。
select()函数阻塞
直到任何一个描述符被激活, 即有数据传输。 从而避免了进程为等待一个已连接上的数据而
无法处理其他连接。 因而, 这是一个时分复用的方法, 从用户角度而言, 它实现了一个进程
或线程中的并发处理。
I/O 多路复用技术的最大优势是系统开销小, 系统不必创建进程、 线程, 也不必维护这
些进程/线程, 从而大大减少了系统的开销。
select()函数用于实现I/O
多路复用, 它允许进程指示系统内核等待多个事件中的任何一
个发生, 并仅在一个或多个事情发送或经过某指定的时间后才唤醒进程。
它的原型如下,
#include<sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set * errorfds, struct timeval *timeout);
ndfs: select() 函数监视描述符数的最大值。 根据进程中打开的描述符数而定, 一般设为要
监视的描述符的最大数加 1。
readfds: select() 函数监视的可读描述符集合。
writefds: select()函数监视的可写描述符集合。
errorfds: select()函数监视的异常描述符集合。
timeout: select()函数超时结束时间
返回值。 如果成功返回总的位数, 这些位对应已准备好的描述符。 否则返回-1, 并在errno
中设置相应的错误码。
FD_ZERO(fd_set *fdset): 清空fdset
与所有描述符的联系
FD_SET(int fd, fd_set *fdset): 建立描述符fd
与 fdset的联系
FD_CLR(int fd, fd_set *fdset): 撤销描述符fd
与 fdset的联系
FD_ISSET(int fd,fd_set *fdset) :: 检查与fdset
联系的描述符 fd
是否可读写, 返回非 0表示可读写。
采用 select()函数实现I/O
多路复用的基本步骤如下:
( 1) 清空描述符集合
( 2) 建立需要监视的描述符与描述符集合的联系
( 3) 调用 select()函数
( 4) 检查所有需要监视的描述符, 利用 FD_ISSET 判断是否准备好
( 5) 对已准备好的描述符进行 I/O 操作
下面是在 eHome 中使用的一个select
函数实例。
// name : Ehome_server.c
// author : pyy
// date : 2008-3-5
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORT 1234 //使用的 port号码
#define MAXSOCKFD 10 //可同时服务的最大连接数目
int main()
{
int sockfd,newsockfd,is_connected[MAXSOCKFD],fd;
struct sockaddr_in addr;
int addr_len = sizeof(struct sockaddr_in);
fd_set readfds;
char buffer[256];
int length;
char buf2[256]; //add
if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0)
{ perror("socket"); exit(1);}
//填写 sockaddr_in
结构
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr))<0)
{ perror("bind"); exit(1);}
if(listen(sockfd,3)<0)
{ perror("listen"); exit(1);}
//清楚连线状态的旗标
for(fd=0;fd<MAXSOCKFD;fd++)
is_connected[fd] = 0;
while(1)
{
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
for(fd=0; fd<MAXSOCKFD; fd++)
if(is_connected[fd]) FD_SET(fd,&readfds);
if(!select(MAXSOCKFD,&readfds,NULL,NULL,NULL)) continue;
//判断是否有新的连线或新信息进来
for(fd=0; fd<MAXSOCKFD; fd++)
if(FD_ISSET(fd,&readfds))
{
if(sockfd == fd)
{
//接收新连线
if((newsockfd= accept(sockfd,(struct sockaddr *)&addr,&addr_len))<0)
perror("accept");
//将欢迎字符串送给 client端
is_connected[newsockfd] = 1;
printf("Connect from %s\n",inet_ntoa(addr.sin_addr));
}
else
{
//接收新信息
bzero(buffer,sizeof(buffer));
if( ( length=read(fd,buffer,sizeof(buffer)) ) <=0)
{
//连线已中断, 清除连线状态旗标
printf("Connection closed.\n");
is_connected[fd] = 0;
close(fd);
}
else
{
printf("Receive message: %s\n",buffer);
write(fd,buffer,length);
bzero(buf2,sizeof(buf2));
sprintf(buf2,"%5.3f%5.3f%5.3f%5.2f%5.3f%5.2f",1.305,2.226,3.333,20.56,9.0,28.5);
length= write(fd,buf2,strlen(buf2));
printf("Send message: %s length= %d\n",buf2,length);
}
}
}
}
}
相关文章推荐
- Linux下面socket编程的非阻塞TCP研究
- socket编程的最简单实例 - linux系统编程及驱动开发 - 小超嵌入式工作室 嵌入式开发学习交流论坛 XC-STC XC2440技术支持 - Powered by Discuz!
- Linux下TCP Socket编程C语言小实例
- 简单的IPv6 UDP/TCP socket编程 -- 两台Linux实现简单的ipv6通信
- Linux下面socket编程的非阻塞TCP研究
- 嵌入式linux下socket网络通信编程实例一
- Linux Socket 编程实例——TCP
- linux socket编程之TCP与UDP
- Linux C socket 编程之TCP
- Linux下Socket编程之TCP应用
- Linux C socket 编程之TCP
- Linux C socket 编程之TCP
- linux socket编程之TCP与UDP
- Linux下面socket编程的非阻塞TCP研究
- Linux下面socket编程的非阻塞TCP
- Linux下基本TCP socket编程之服务器端
- Linux下基本TCP socket编程之服务器端
- linux socket网络编程二 基于tcp
- Linux下Socket编程之TCP Server端
- linux下TCP socket编程初步(1)