上篇文章中对一些函数有了详细的介绍,本篇使用这些函数来实现基于TCP的socket编程
服务器程序端:
#include <iostream> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <pthread.h> #include <arpa/inet.h> #include <netinet/in.h> #define BLOCK 6//等待连接队列的允许最大长度,在listen()中用到 using namespace std; typedef struct { int port; int fd; }C; void usage(string proc)//输入参数错误会调用该函数 { cout<<proc<<" [ip] [port]"<<endl; } void *thread_run(void *arg) { char buf[1024]; while(1){ memset(buf,‘\0‘,sizeof(buf)); int size=recv(((C*)arg)->fd,buf,sizeof(buf)-1,0); if(size>0){ cout<<"client# "<<buf<<endl; }else if (size==0){ continue; break; }else{ cerr<<strerror(errno)<<endl; } } close(((C*)arg)->fd); } int create_sock(char *port,const char * inaddr) { //1.创建一个监听套接字 int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd<-1){ cerr<<strerror(errno)<<endl; exit(1); } //2.创建用来保存本地信息的结构体,用于bind struct sockaddr_in local; local.sin_family=AF_INET; int _port=atoi(port); local.sin_port=htons(_port); //下面三条语句实现的功能是一样的 local.sin_addr.s_addr=inet_addr(inaddr); // local.sin_addr.s_addr=inet_network(inaddr); // inet_aton(inaddr,&local.sin_addr); //3.用来绑定套接字listenfd和本地ip和端口号等 if(bind(listenfd,(struct sockaddr*)&local,sizeof(local))<0){ cerr<<strerror(errno)<<endl; exit(2); } //4.设置监听状态 if(listen(listenfd,BLOCK)<0){ cerr<<strerror(errno)<<endl; exit(3); } return listenfd; } int main(int argc,char* argv[]) { if(argc!=3){ usage(argv[0]); exit(1); } int listen_fd=create_sock(argv[2],argv[1]); struct sockaddr_in client;//保存客户端的信息 socklen_t len=sizeof(client); while(1){ //从listen_fd的请求连接队列中取出一个请求并创建一个新的套接字,这个新的套接字用来和客户端进行通信 int connfd=accept(listen_fd,(struct sockaddr*)&client,&len); if(connfd<0){ continue; } cout<<"get a connect..."<<" sock : "<<connfd <<" ip: "<<inet_ntoa(client.sin_addr)<<" port: " <<ntohs(client.sin_port)<<endl; //以下用到了3个版本的 //1.单进程,这个版本只能连接一个客户端,当有第二条连接请求到来时,会被阻塞在accept处 #ifdef _V1_ char buf[1024]; while(1){ memset(buf,‘\0‘,sizeof(buf)); int size=recv(connfd,&buf,sizeof(buf)-1,0); if(size>0){ cout<<"client# "<<buf<<endl; }else if (size==0){//client close close(connfd); }else{ cerr<<strerror(errno)<<endl; } } //2.多进程,每有一条连接请求到来的时候,都会创建出一个子进程,由子进程来完成通信,父进程则一直处于监听状态,这样就可以在多条连接上通信 #elif _V2_ pid_t id=fork(); if(id==0){ close(listen_fd); }else if(id>0){ close(connfd); break; }else{ cerr<<strerror(errno)<<endl; exit(4); } char buf[1024]; while(1){ memset(buf,‘\0‘,sizeof(buf)); int size=recv(connfd,&buf,sizeof(buf)-1,0); if(size>0){ cout<<"client# "<<buf<<endl; }else if (size==0){ break; }else{ } } //3.多线程,由于创建进程的开销比较大,所以用多线程来实现 #elif _V3_ pthread_t tid; C info; info.port=client.sin_port; info.fd=connfd; int err=pthread_create(&tid,NULL,thread_run,(void*)&info); if(err!=0){ cerr<<strerror(errno)<<endl; exit(5); } //线程被设置成可分离状态,当线程函数通信完成之后,由系统自动回收资源 if(pthread_detach(tid)<0){ cerr<<errno<<endl; } #else cout<<"default"<<endl; #endif } return 0; }
客户端程序:
#include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <string> #include <errno.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <netinet/in.h> using namespace std; void usage(string proc)//输入参数错误会调用该函数,和server端一样 { cout<<proc<<" [ip] [port]"<<endl; } int creat_socket() { //创建套接字,该套接字可直接用于和服务器端进行通信 int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0){ cerr<<strerror(errno)<<endl; exit(1); } return fd; } int main(int argc,char* argv[]) { if(argc!=3){ usage(argv[0]); exit(1); } int fd=creat_socket(); int _port=atoi(argv[2]); //创建结构体用于保存服务器端信息 struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_port=htons(_port); inet_aton(argv[1],&addr.sin_addr); socklen_t addrlen=sizeof(addr); //主动连接服务器端 if(connect(fd,(struct sockaddr*)&addr,addrlen)<0){ cerr<<strerror(errno)<<endl; exit(2); } //给服务器端发送数据 char buf[1024]; while(1){ memset(buf,‘\0‘,sizeof(buf)); cin>>buf; if(send(fd,buf,sizeof(buf)-1,0)<0){ cerr<<strerror(errno)<<endl; continue; } cout<<"I say#"<<buf<<endl; } return 0; }
Makefile
bin_server=tcp_server bin_client=tcp_client src_server=tcp_server.cpp src_client=tcp_client.cpp cc=g++ .PHONY:all all:$(bin_server) $(bin_client) $(bin_server):$(src_server) cc -o [email protected] $^ -lstdc++ -D_V3_ -lpthread -g//此处使用的是服务器端的_V3_版本 $(bin_client):$(src_client) cc -o [email protected] $^ -lstdc++ -g .PHONY:clean clean: rm -f $(bin_server) $(bin_client)
以上程序实现了客户端和服务器端的通信,客户端可以向服务器端直接发送消息
如果此时Ctrl+C服务器端程序,再次运行服务器端程序,会出现以下一句话:
Address already in use
这是因为主动关闭连接的一方会进入TIME_WAIT状态,linux下一般为30秒,这样我们必须等上一段时间才可以重启服务器端程序,但我们有一种方法可以使主动关闭的一方不用进入TIME_WAIT状态
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字
level:选项定义的层次;支持SOL_SOCKET(基本套接口)、IPPROTO_TCP(TCP套接口)、 IPPROTO_IP( IPv4套接口)和IPPROTO_IPV6(IPv6套接口)
optname:需设置的选项
optval:指针,指向存放选项待设置的新值的缓冲区
optlen:optval缓冲区长度
SO_REUSEADDR // 允许套接口和一个已在使用中的地址捆绑
在socket()和bind()之间插入下面代码,设置listenfd描述符
struct linger lig; int iLen; lig.l_onoff=1; lig.l_linger=0; iLen=sizeof(struct linger); setsockopt(listenfd,SOL_SOCKET,SO_LINGER,(char *)&lig,iLen);
setsockopt()详解链接:http://blog.sina.com.cn/s/blog_6ede0d160100q9li.html
《完》
时间: 2024-10-07 13:48:57