1、TCP与UDP的区别
Tcp是一种面向连接的,可靠的字节流服务。(设有数据包编号与差错控制机制。)
特点:
由于网络的复杂性,传输信息时,数据包可能会丢失,差错控制中的确认机制在接收到数据包是发送确认信息,若是数据包丢失,则回发数据包编号,让对方重新发送;
由于网络的复杂性,传输信息时有多种网络传送途径可以选择,数据包被接收的顺序与发送顺序不同,可以根据数据包的编号,将数据包重组。
优点:
网络连接是以点对点的形式,加上上述特点,保证了数据的安全性,数据包不会中途被劫。
缺点:耗费资源很多。
UDP是无连接的,不可靠的数据协议报。通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据(没有类似TCP的差错控制和数据包编号机制)。
特点:通讯速度比较快,不需要TCP的三次握手。
1)多播:
与发送主机的子网相同的主机,都会在自己的端口收到主机发出来的UDP消息(发送到(包括本机)所有在这个网络地址的主机上,例如192.168.0.255,则在192.168.0.的主机都会收到)。消息会被复制并发到每个主机的网卡上去,网卡收到消息后提交给操作系统去处理,操作系统发现有程序在端口接收UDP数据则把消息转给相应的程序去处理,如果没有程序接收来自该端口的UDP消息,则操作系统丢弃该消息。
2)单播:
则消息只会从主机发到特定的主机上,主机的网卡收到消息后转给操作系统去处理,操作系统再把此消息转给相应程序去处理,如果没有程序处理就丢弃该包。
3)组播:
则消息只会从主机发到加入了规定组的主机的端口。象广播一样,组播消息一样会被复制发到网络所有主机的网卡上,但只有宣布加入这个组的主机的网卡才会把数据提交给操作系统去处理。如果没有加入组,则网卡直接将数据丢弃。要想接收组播消息的主机必须运行命令加入组。
2、TCP和UDP编程
(1)UDP广播
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include <pthread.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;
void rev()
{
int ret;
char buffer[BUFFER_SIZE];
uint32_t len=sizeof(recvaddr);
while(1)
{
if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)//监听地址中的socket读事件
{
perror("ERROR reading from socket");
break;
}
buffer[ret]=0;
printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
}
}
int main(int argc,char **argv)
{
int opt=1;
int ret=RET_OK;
char buffer[BUFFER_SIZE];
struct hostent *server;
if(argc<2)
{
fprintf(stderr,"usage &s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL)
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
herror("ERROR opening socket");
return RET_ERR;
}
//设置广播套接字
if((setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt)))<0)
{
perror("ERROR setsockopt");
goto failed;
}
memset(&servaddr,0,sizeof(servaddr));//服务器地址
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序,或者INADDR_BROADCAST
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
pthread_create(&t1, NULL, (void*)rev, NULL);
while(1)//不断发送数据到指定地址端口
{
printf("Enter the message : ");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
if((ret=sendto(sockfd,buffer,strlen(buffer)+1,0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
{
perror("ERROR writing to socket");
break;
}
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
服务器端:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int main(int agrc,char **argv)
{
int sockfd,opt=1;
uint32_t len;
struct sockaddr_in cliaddr;
uint8_t buffer[BUFFER_SIZE];
int ret=RET_OK;
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
perror("ERROR opening socket");
return RET_ERR;
}
//SO_REUSEADDR允许重用本地址
if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
{
perror("ERROR setsockopt");
goto failed;
}
memset(&cliaddr,0,sizeof(cliaddr));
cliaddr.sin_family=AF_INET;
cliaddr.sin_addr.s_addr=INADDR_ANY;//监听所有地址发来的指定端口
cliaddr.sin_port=htons(ECHO_PORT);
if((ret=bind(sockfd,(struct sockaddr*)&cliaddr,sizeof(cliaddr)))<0)//绑定地址(udp不绑定也是可以的)
{
perror("ERROR on binding");
goto failed;
}
do//不断接收数据并返回相同的数据
{
len=sizeof(cliaddr);
if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&cliaddr,&len))>0)
{
buffer[ret] = 0;
printf("[%s:%d]%s",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),buffer);
ret=sendto(sockfd,buffer,ret,0,(struct sockaddr*)&cliaddr,len);
}
}while(ret>=0);
failed:
close(sockfd);
return 0;
}
运行过程:用ifconfig eth0 设置ip,并查看广播地址作为client.c的参数运行
运行结果:多个服务器可以同时接收来自客户端的信息
(2)UDP指定地址接受
UDP,client中使用connect指定接受地址。
指定接收数据的地址,其他IP地址的数据拒绝接收。
sendto,recvfrom可以用write和read代替.
客户端代码:
int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;
void rev()
{
int ret;
char buffer[BUFFER_SIZE];
uint32_t len=sizeof(recvaddr);
while(1)
{
if((ret=read(sockfd,buffer,sizeof(buffer)-1,0))<0)
{
perror("ERROR reading from socket");
break;
}
buffer[ret]=0;
printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
//printf("Server echo message: %s\n",buffer);
}
}
int main(int argc,char **argv)
{
int opt=1;
int ret=RET_OK;
char buffer[BUFFER_SIZE];
struct hostent *server;
if(argc<2)
{
fprintf(stderr,"usage &s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL)
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
herror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
if((ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))<0) //创建连接
{
perror("ERROR connecting");
goto failed;
}
pthread_create(&t1, NULL, (void*)rev, NULL);
while(1)
{
printf("Enter the message : ");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
if((ret=write(sockfd,buffer,strlen(buffer)+1,))<0)
{
perror("ERROR writing to socket");
break;
}
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
(3)UDP单播
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include <pthread.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int sockfd;
struct sockaddr_in servaddr,recvaddr;
pthread_t t1;
void rev()
{
int ret;
char buffer[BUFFER_SIZE];
uint32_t len=sizeof(recvaddr);
while(1)
{
if((ret=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)
{
perror("ERROR reading from socket");
break;
}
buffer[ret]=0;
printf("[form:%s:%u]%s\n",inet_ntoa(recvaddr.sin_addr),ntohs(recvaddr.sin_port), buffer);
}
}
int main(int argc,char **argv)
{
int opt=1;
int ret=RET_OK;
char buffer[BUFFER_SIZE];
struct hostent *server;
if(argc<2)
{
fprintf(stderr,"usage &s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL)
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))<0)
{
herror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
pthread_create(&t1, NULL, (void*)rev, NULL);
while(1)
{
printf("Enter the message : ");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
if((ret=sendto(sockfd,buffer,strlen(buffer)+1,0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
{
perror("ERROR writing to socket");
break;
}
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
服务器端同上;
运行过程:./client ip地址;
运行结果:一个服务器终端可以接收数据。
(4)支持多人聊天和私聊的客户端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
struct sockaddr_in recvaddr;
char buffer[BUFFER_SIZE];
void recv2(void* sockfd)
{
uint32_t len=sizeof(recvaddr);
int ret;
while(1)
{
if((ret=recvfrom((int)sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&recvaddr,&len))<0)
{
perror("ERROR reading from socket");
break;
}
buffer[ret]=0;
printf("Server echo message: %s\n",buffer);
}
}
int main(int argc,char **argv)
{
int sockfd;
pthread_t t1;
int ret=RET_OK;
struct sockaddr_in servaddr,tempaddr;
struct hostent *server;
char tempbuf[BUFFER_SIZE],buf[BUFFER_SIZE];
<span style="white-space:pre"> </span>
if(argc<2)
{
fprintf(stderr,"usage &s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL)
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_DGRAM, IPPROTO_UDP))<0)
{
herror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
<span style="white-space:pre"> </span>
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
<span style="white-space:pre"> </span>
pthread_create(&t1,NULL,(void *)recv2,(void *)sockfd);
while(1)
{
printf("Enter the message : ");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)break;
<span style="white-space:pre"> </span>
if(strstr(buffer,":") && strstr(buffer,"@")) /*私人聊天*/
{
tempaddr.sin_family=AF_INET;
tempaddr.sin_addr.s_addr=inet_addr(strtok(buffer,":"));//地址
tempaddr.sin_port=htons(atoi(strtok(NULL,"@")) );
char *del=":@";
strcpy(tempbuf,strtok(NULL,del));
if((ret=sendto(sockfd,tempbuf,strlen(tempbuf),0,(struct sockaddr*)&tempaddr,sizeof(tempaddr)))<0)
{
<span style="white-space:pre"> </span>perror("ERROR writing to socket");
<span style="white-space:pre"> </span>break;
}
}
else/*多人聊天*/
{
if((ret=sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
{
perror("ERROR writing to socket");
break;
}
}
}
<span style="white-space:pre"> </span>
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
服务器端同上
(5) TCP半双工聊天
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int main(int argc,char **argv)
{
int sockfd,ret=RET_OK;
struct sockaddr_in servaddr;
struct hostent *server;
char buffer[BUFFER_SIZE];
if(argc<2)
{
fprintf(stderr,"usage %s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL) //由域名获取IP地址
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) //创建套接字
{
perror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
if((ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))<0) //创建连接
{
perror("ERROR connecting");
goto failed;
}
while(1)
{
printf("\tEnter the message:");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL) //输入字符到buffer指针
{
break;
}
if((ret=write(sockfd,buffer,strlen(buffer)))<0) //把buffer的内容 写入strlen(buffer)的字节到sockfd的socket中
{
perror("ERROR write to socket");
break;
}
if((ret=read(sockfd,buffer,sizeof(buffer)-1))<0)//读取到buffer中
{
perror("ERROR read from socket");
break;
}
else
{
buffer[ret]='\0';
printf("\tServer echo message: %s\n",buffer);
}
if(ret==0)
{
printf("Server disconnect\n");
break;
}
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
服务器端:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
char buffer[BUFFER_SIZE];
从socket读一行数据到缓冲区
static int my_readline(int fd,void *buf,unsigned len)
{
int n,rc;
char c,*ptr;
ptr=buf;
for(n=1;n<len;n++)
{
again:
if((rc=read(fd,&c,1))==1)
{
*ptr++=c;
if(c=='\n')break;
}
else if(rc==0)
{
if(n==1)return 0;
else break;
}else
{
if(errno==EINTR)goto again;
return -1;
}
}
*ptr=0;
return n;
}
static int proc_echo(int sockfd)
{
int ret;
while(1)
{
if((ret=my_readline(sockfd,buffer,sizeof(buffer)-1))<0)
{
perror("read");
return -1;
}else if(ret==0)
{
printf("client disconnect.\n");
return 0;
}
buffer[ret]='\0';
printf("client echo message: %s\n",buffer);
printf("\tEnter message:\n ");
fflush(stdout);
if(fgets(buffer,sizeof(buffer),stdin)==NULL) //输入字符到buffer指针
{
break;
}
if((ret=write(sockfd,buffer,strlen(buffer)))<0) //把buffer的内容 写入strlen(buffer)的字节到nsock的socket中
{
perror("ERROR write to socket");
break;
}
}
return -1;
}
int main(int argc,char **argv)
{
int sockfd,nsock,ret=0;
uint32_t len;
struct sockaddr_in servaddr,cliaddr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=INADDR_ANY;
servaddr.sin_port=htons(ECHO_PORT);
if((ret=bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
{
perror("ERROR on binding");
goto failed;
}
if((ret=listen(sockfd,LISTEN_QUEUE_NUM))!=0)
{
perror("ERROR on listen");
goto failed;
}
printf("\tserver is listening>>>\n");
while(1)
{
len=sizeof(cliaddr);
if((nsock=accept(sockfd,(struct sockaddr*)&cliaddr,(uint32_t*)&len))<0)
{
perror("accept");
break;
}
printf("\tclient connect success!!!\n");
proc_echo(nsock);
close(nsock);
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
运行:
./ser, server is listening>>>
./cli 服务器地址;
Enter: ......
服务器:读出数据,并从键盘接收数据回发给客户;(此时客户端阻塞直到收到服务器的回信).
(6)TCP全双工通信
客户端:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include"echo_net.h"
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
static void proc_echo(void *sockfd)
{
int ret;
char buffer[BUFFER_SIZE];
while(1)
{
if((ret=read((int)sockfd,buffer,sizeof(buffer)-1))<0)//读取到buffer中
{
perror("ERROR read from socket");
break;
}
if(ret==0)
{
printf("Server disconnect\n");
//pthread_cancel(t2);
break;
}
buffer[ret]='\0';
printf("\tServer echo message: %s\n",buffer);
}
}
int main(int argc,char **argv)
{
int sockfd,ret=RET_OK;
pthread_t t2;
struct sockaddr_in servaddr;
struct hostent *server;
char buffer[BUFFER_SIZE] ;
if(argc<2)
{
fprintf(stderr,"usage %s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL) //由域名获取IP地址
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) //创建套接字
{
perror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
if((ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))<0) //创建连接
{
perror("ERROR connecting");
goto failed;
}
pthread_create(&t2,NULL,(void *)proc_echo,(void*)sockfd);
while(1)
{
printf("\tEnter the message:\n");
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)//输入字符到buffer指针
{
break;
}
if((ret=write(sockfd,buffer,strlen(buffer)))<0)//把buffer的内容 写入strlen(buffer)的字节到sockfd的socket中
{
perror("ERROR write to socket");
break;
}
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
服务器端:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include <pthread.h>
#include"echo_net.h"
#define BUFFER_SIZE 256
static int my_readline(int fd,void *buf,unsigned len)
{
int n,rc;
char c,*ptr;
ptr=buf;
for(n=1;n<len;n++)
{
again:
if((rc=read(fd,&c,1))==1)
{
*ptr++=c;
if(c=='\n')break;
}else if(rc==0)
{
if(n==1)return 0;
else break;
}else
{
if(errno==EINTR)goto again;
return -1;
}
}
*ptr=0;
return n;
}
static void proc_echo_2(void *sockfd)
{
int ret;
char buffer[BUFFER_SIZE];
char readBuf[BUFFER_SIZE];
while(1)
{
//消息接收
if((ret=my_readline(sockfd,readBuf,sizeof(buf)-1))<0)
{
perror("read");
return -1;
}else if(ret==0)
{
printf("client disconnect.\n");
pthread_cancel(t2);
return 0;
}
buf[ret]='\0';
printf("client echo message: %s",readBuf);
fflush(stdout);
//消息发送
printf("\tEnter message:\n ");
if(fgets(buffer,sizeof(buffer),stdin)==NULL) //输入字符到buffer指针
{
printf("没有输入字符");
continue;
}
if((ret=write((int)sockfd,buffer,strlen(buffer)))<0) //把buffer的内容 写入strlen(buffer)的字节到nsock的socket中
{
perror("ERROR write to socket");
break;
}
}
close(nsock);
}
int main(int argc,char **argv)
{
int sockfd,nsock,ret=0;
int opt;pthread_t t2;
char buf[32];
uint32_t len;
struct sockaddr_in servaddr,cliaddr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
perror("ERROR opening socket");
return RET_ERR;
}
//SO_REUSEADDR允许重用本地址
if((ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
{
perror("ERROR setsockopt");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=INADDR_ANY;
servaddr.sin_port=htons(ECHO_PORT);
if((ret=bind(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))<0)
{
perror("ERROR on binding");
goto failed;
}
if((ret=listen(sockfd,LISTEN_QUEUE_NUM))!=0)
{
perror("ERROR on listen");
goto failed;
}
printf("\tserver is listening>>>\n");
while(1)
{
len=sizeof(cliaddr);
if((nsock=accept(sockfd,(struct sockaddr*)&cliaddr,(uint32_t*)&len))<0)
{
perror("accept");
break;
}
printf("\tclient connect success!!!\n");
//printf("\tclient id =%s\n",inet_ntoa(cliaddr.sin_addr));
printf("client id =%s\n",
inet_ntop(cliaddr.sin_family,&cliaddr.sin_addr,buf,sizeof(buf)));
printf("\tport =%d\n",ntohs(cliaddr.sin_port));
pthread_create(&t2,NULL,(void *)proc_echo_2,(void*)sockfd);
proc_echo(nsock);
}
failed:
close(sockfd);
return ret<0?RET_ERR:RET_OK;
}
(7) TCP I/O复用之select
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<errno.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int main(int argc,char **argv)
{
int maxfd=0;
fd_set rset,set;
int nfound,bytesread;
int sockfd;
int ret=RET_OK;
struct sockaddr_in servaddr;
struct hostent *server;
char buffer[BUFFER_SIZE];
if(argc<2)
{
fprintf(stderr,"usage &s hostname\n",argv[0]);
return RET_ERR;
}
if((server=gethostbyname(argv[1]))==NULL)
{
herror("gethostbyname.");
return RET_ERR;
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
herror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));//申请信息表空间
servaddr.sin_family=AF_INET;//设为IPv4
servaddr.sin_addr.s_addr=*(uint32_t *)server->h_addr;//地址转换,32位大端字序
servaddr.sin_port=htons((uint16_t)ECHO_PORT);//设定端口,16位大端字序转换
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("ERROR connect");
return RET_ERR;
}
maxfd=fileno(stdin);//获取标准读的文件描述符
FD_ZERO(&set);
FD_SET(sockfd,&set);//把连接socket加入文件描述符集
FD_SET(maxfd,&set);//把标准输入输出描述符加入文件描述符集
maxfd=(maxfd>sockfd?maxfd:sockfd)+1;//监听数是最大的文件描述符+1
while(1)
{
rset=set;
if((nfound=select(maxfd,&rset,(fd_set*)0,(fd_set*)0,NULL))<0)
{
if(errno==EINTR)
{
fprintf(stderr,"interrupted system call\n");
continue;
}
perror("select");
exit(1);
}
if(FD_ISSET(fileno(stdin),&rset))//如果是标准输入事件,则输入字符到缓冲区,发送缓冲区数据
{
if(fgets(buffer,sizeof(buffer)-1,stdin)==NULL)
{
if(ferror(stdin))
{
perror("stdin");
return -1;
}
return 0;
}
if(write(sockfd,buffer,strlen(buffer))<0)
{
perror("ERROR writing");
return -1;
}
}
if(FD_ISSET(sockfd,&rset))//如果是socket读事件(这里socket只有读事件,监听的是读文件描述符集),则读取socket缓冲区的数据到缓存
{
if((bytesread=read(sockfd,buffer,sizeof(buffer)))<0)
{
perror("ERROR read");
exit(1);
}else if(bytesread==0)//如果有读事件而读缓冲区是0字节,表示对端已关闭socket
{
fprintf(stderr,"server disconnect\n");
exit(0);
}
buffer[bytesread]=0;//最后加上结束符
printf("buffer: %s",buffer);//输出读取socket的数据
}
}
return 0;
}
服务器端:
#include<sys/types.h>
#include<ctype.h>
#include<strings.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<errno.h>
#include<sys/time.h>
#include<stdio.h>
#include<string.h>
#include<sys/select.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/wait.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int main(int argc,char **argv)
{
struct sockaddr_in servaddr,remote;
int request_sock,new_sock;
int nfound,fd,maxfd,bytesread;
uint32_t addrlen;
fd_set rset,set;
struct timeval timeout;
char buf[BUFFER_SIZE];
if((request_sock=socket(AF_INET,SOCK_STREAM, IPPROTO_TCP))<0)
{
perror("ERROR opening socket");
return RET_ERR;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=INADDR_ANY;
servaddr.sin_port=htons((uint16_t)ECHO_PORT);
if(bind(request_sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)//绑定socket到地址(设置socket监听方式)
{
perror("ERROR on binding");
return RET_ERR;
}
if(listen(request_sock,LISTEN_QUEUE_NUM)<0)//开始监听socket
{
perror("ERROR on listen");
return RET_ERR;
}
FD_ZERO(&set);
FD_SET(request_sock,&set);
maxfd=request_sock;
while(1)
{
rset=set;
timeout.tv_sec=0;
timeout.tv_usec=50000000;.//最大50ms监听超时时间
if((nfound=select(maxfd+1,&rset,(fd_set*)0,(fd_set*)0,&timeout))<0)//非阻塞监听读事件(设置了最大监听事件描述符)
{
perror("select");
return -1;
}
else if(nfound==0)//没有触发事件则继续监听
{
fflush(stdout);
continue;
}
if(FD_ISSET(request_sock,&rset))
{
addrlen=sizeof(remote);
if((new_sock=accept(request_sock,(struct sockaddr*)&remote,&addrlen))<0)
{
perror("accept");
return -1;
}
printf("connection from host %s,port %d,socket %d\r\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port),new_sock);
FD_SET(new_sock,&set);//接收新的连接后,把连接加入需要监听的文件描述符集
if(new_sock > maxfd)maxfd=new_sock;
FD_CLR(request_sock,&rset);
nfound--;
}
for(fd=0;fd<=maxfd&&nfound>0;fd++)
{
if(FD_ISSET(fd,&rset))
{
nfound--;
if((bytesread=read(fd,buf,sizeof(buf)-1))<0)
{
perror("read");
}
if(bytesread==0)//对端关闭了
{
fprintf(stderr,"server: end of file on %d\r\n",fd);
FD_CLR(fd,&set);
close(fd);
continue;
}
buf[bytesread]=0;
printf("%s: %d bytes from %d: %s\n",argv[0],bytesread,fd,buf);
if(write(fd,buf,bytesread)<0)//echo 业务,把读到的数据返回给客户端
{
perror("echo");//写入有误
FD_CLR(fd,&set);//减少需要监听的socket
close(fd);
}
}
}
}
return 0;
}
(8)TCP I/O复用之poll
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<errno.h>
#include<ctype.h>
#include<netdb.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/select.h>
#include<sys/poll.h>
#define RET_OK 0
#define RET_ERR -1
#define LISTEN_QUEUE_NUM 5
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
int main(int argc, char **argv)
{
int sock;
struct sockaddr_in servaddr;
struct hostent *server;
static struct pollfd cpoll[2];
int nfound, bytesread;
char buf[BUFFER_SIZE];
if(argc < 2)
{
fprintf(stderr, "usage %s hostname\n", argv[0]);
return RET_ERR;
}
if((server = gethostbyname(argv[1])) == NULL)
{
perror("gethostbyname. ");
return RET_ERR;
}
/*setup socket*/
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)//tcp socket
{
perror("socket");
return -1;
}
/*fillup ip address structure*/
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = *(uint32_t *) server->h_addr;
servaddr.sin_port = htons((uint16_t) ECHO_PORT);
/*connect to server*/
if(connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) //连接到对端
{
perror("connect");
return -1;
}
//加入需要监听的文件描述符以及相关的事件
/*fill up poll struct*/
cpoll[0].fd = fileno(stdin);//加入监听描述符
cpoll[0].events = POLLIN;//加入监听事件
cpoll[1].fd = sock;//加入监听描述符
cpoll[1].events = POLLIN;//加入监听事件
while (1)
{
if ((nfound = poll(cpoll, 2, -1)) < 0)//阻塞监听poll数组事件(socket事件),(在没有别的业务的情况下可以阻塞接收)
{
if (errno == EINTR)//是中断事件的错误,则忽略
{
fprintf(stderr, "interruptedb system call\n");
continue;
}
perror("select");
exit(1);
}
if (cpoll[0].revents & (POLLIN | POLLERR))//检查输入设备的读事件
{
if(fgets(buf, sizeof(buf), stdin) == NULL) //从标准输入设备描述符的缓冲区读取数据到用户缓冲区
{
if (ferror(stdin))
{
perror("stdin");
return -1;
}
return 0;
}
/*write to socket*/
if (write(sock, buf, strlen(buf)) < 0)
{
perror("write");
return -1;
}
}
if(cpoll[1].revents & (POLLIN | POLLERR))//检查socket的读事件
{
if((bytesread = read(sock, buf, sizeof(buf))) < 0)//从socket的缓冲区读取数据到用户缓冲区
{
perror("read");
exit(1);
}
else if(bytesread == 0)//对端关闭,则关闭socket
{
fprintf(stderr, "server disconnect\n");
exit(0);
}
buf[bytesread] = 0;
printf("%s\n", buf);
}
}
return 0;
}
服务器端:
#include<sys/types.h>
#include<ctype.h>
#include<strings.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<errno.h>
#include<sys/time.h>
#include<stdio.h>
#include<string.h>
#include<sys/poll.h>
#include<stdlib.h>
#define LISTEN_QUEUE_NUM 5
#define MAX_CONNECT 1024
#define BUFFER_SIZE 256
#define ECHO_PORT 2029
static struct pollfd clipoll[MAX_CONNECT];
int max = 0;//poll数组中的最大的当前使用的文件描述符
static void poll_init()//初始化poll结构数组
{
int i;
for (i = 0; i < MAX_CONNECT; i++)
clipoll[i].fd = -1;
}
static int poll_alloc()//获取一个没用的poll结构数组空槽的下标,找不到则返回-1
{
int i;
for (i = 0; i < MAX_CONNECT; i++)
{
if (clipoll[i].fd < 0)
return i;
}
return -1;
}
static void update_max()//获取在使用的文件描述符在poll数组中的下标中的最大的一个
{
int i;
max = 0;
for (i = 0; i < MAX_CONNECT; i++)
{
if (clipoll[i].fd > 0)
max = i;
}
}
static int poll_free(int i)//释放pollfd结构数组的一个成员
{
close(clipoll[i].fd);
clipoll[i].fd = -1;
clipoll[i].events = 0;
clipoll[i].revents = 0;
update_max();
}
static int poll_set_item(int fd, uint32_t events)//加入一个需要监听的文件描述符(和相关事件)到poll数组的一个空槽中
{
int i;
if ((i = poll_alloc()) < 0)//获取一个pollfd结构,加入文件描述符,和对应的事件
return -1;
clipoll[i].fd = fd;
clipoll[i].events = events;
clipoll[i].revents = 0;//清空读事件
if (i > max)
max = i;
return 0;
}
int main(int argc, char **argv)
{
struct sockaddr_in servaddr, remote;
int request_sock, new_sock;
int nfound, i, bytesread;
uint32_t addrlen;
char buf[BUFFER_SIZE];
/*setup socket*/
if ((request_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
return -1;
}
/*fill up ip address structure*/
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//协议簇
servaddr.sin_addr.s_addr = INADDR_ANY;//监听所有地址
servaddr.sin_port = htons(ECHO_PORT);//监听端口
if (bind(request_sock, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)//设置socket监听方式(绑定socket到地址)
{
perror("bind");
return -1;
}
if (listen(request_sock, LISTEN_QUEUE_NUM) < 0)//开始socket监听事件
{
perror("listen");
return -1;
}
poll_init(); //初始化poll数组
poll_set_item(request_sock, POLLIN);//把需要监听的socket(和其读事件)加入到poll数组
while (1)
{
if ((nfound = poll(clipoll, max + 1, 50)) < 0)//非阻塞监听poll文件描述符的事件(max是poll数组中的在使用的文件描述符最大的下标),超时时间为50ms
{
if (errno == EINTR)//忽略中断的错误
{
printf("interruptedb system call\n");
continue;
}
perror("poll");
return -1;
}
else if(nfound == 0)//没有监听到发生事件的文件描述符
{
fflush(stdout);
continue;
}
if (clipoll[0].revents & (POLLIN | POLLERR))//第一个是监听socket,若其发送了读事件,则接受新连接
{
addrlen = sizeof(remote);
if ((new_sock = accept(request_sock, (struct sockaddr *) &remote, &addrlen)) < 0)
{
perror("accept");
return -1;
}
printf("connection fromm host %s,port %d, socket %d\r\n",
inet_ntoa(remote.sin_addr), ntohs(remote.sin_port), new_sock);
if (poll_set_item(new_sock, POLLIN) < 0)//加入新的连接到poll数组
fprintf(stderr, "Too many connects\r\n");
nfound--;
}
for (i = 1; i <= max && nfound > 0; i++)//遍历检查通信socket是否有触发事件
{
if (clipoll[i].fd >= 0 && clipoll[i].revents & (POLLIN | POLLERR))//poll数组的socket文件描述符有读事件
{
nfound--;
if((bytesread = read(clipoll[i].fd, buf, sizeof(buf) - 1)) < 0)//读取socket 的数据到用户缓冲区
{
perror("read");
poll_free(i);
continue;
}
if(bytesread == 0)//对端关闭了
{
fprintf(stderr, "server: end of file on %d\r\n", clipoll[i].fd);
poll_free(i);
continue;
}
buf[bytesread] = 0;
printf("%s:%d bytes from %d :%s\n", argv[0], bytesread,
clipoll[i].fd, buf);
if (write(clipoll[i].fd, buf, bytesread) != bytesread)//echo业务,返回读取的数据到对端
{
perror("echo");
poll_free(i);//写错误(表示对端关闭了)则关闭socket
continue;
}
}
}
}
return 0;
}