上篇文章中对一些函数有了详细的介绍,本篇使用这些函数来实现基于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 $@ $^ -lstdc++ -D_V3_ -lpthread -g//此处使用的是服务器端的_V3_版本 $(bin_client):$(src_client) cc -o $@ $^ -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
《完》
本文出自 “零蛋蛋” 博客,请务必保留此出处http://lingdandan.blog.51cto.com/10697032/1775685
原文地址:http://lingdandan.blog.51cto.com/10697032/1775685