建立一个最简单的socket类——网络编程初步
2005-01-04 18:28
288 查看
建立一个最简单的socket类——网络编程初步
fancy于2005年1月4日
网络编程大概是程序员最为热衷的技术之一,本人在无聊的课余稍稍翻阅了那本很著名的大部头《tcp/ip协议详解》。于是尝试着封装了一个极为简陋的socket类,通过编码调试,惊喜的发现它确实可以实现一些简单的传输功能。个人欣慰的同时将其发布出来,希望可以给像我一样蹩脚并挣扎于茫茫黑夜中的各位同仁一点帮助。
Socket是使用Unix文件描述符和其他程序通讯的方式。我们利用系统调用socket()。它返回套接口描述符,我们可以通过这个套接口描述符来调用send()和recv()。
Internet套接字有两种类型。一种是“Stream Socket”,流式套接口;另一种是“Datagram Socket”,数据报套接口,也叫无连接套接口。
流式套接口是可靠的双向通讯的数据流,它是无错误的传递数据的,有自己的错误控制。流式套接口的高质量数据传输是靠了“TCP”协议来实现。像qq,popo的文件传输就是使用这种方法。
数据报套接口它是不可靠的,当你发送一个数据报的时候,它可能到达,可能数据次序会颠倒。它使用的是“UDP”协议。
了解了这些,现在让我们来看一段真正的程序:
//server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 6140
#define BUFSIZE 100
main()
{
int sockfd;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
char buf[BUFSIZE];
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
printf("socket error/n");
exit(1);
}
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(MYPORT);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1){
printf("bind error");
exit(1);
}
int addr_len=sizeof(struct sockaddr);
int numbytes;
if((numbytes=recvfrom(sockfd,buf,BUFSIZE,0,(struct sockaddr *)&their_addr,&add_len))==-1){
printf("recvfrom error");
exit(1);
}
printf("Get package from %s/n",inet_ntoa(their_addr.sin_addr));
printf("Package is %d bytes/n",numbytes);
buf[numbytes]='/0';
printf("Package is /"%s/"/n",buf);
close(sockfd);
}
//client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 6140
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in their_addr;
struct hostent *he
if(argc<3){
printf("lees argument/n");
exit(1);
}
if((he=gethostbyname(argv[1]))==NULL){
printf("gethostbyname error/n");
exit(1);
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
printf("socket error/n");
exit(1);
}
their_addr.sin_family=AF_INET;
their_addr.sin_port=htons(MYPORT);
their_addr.sin_addr=*((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero),8);
int numbytes;
if((numbytes=sendto(sockfd,argv[2],strlen(argv[2],0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)))==-1){
printf("sentto error/n");
exit(1);
}
printf("Send %d bytes to %s/n",numbytes,inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
}
从上面的两段程序,我们可以看出,通过套接字发送和接受数据的步骤基本如下:
首先使用socket()建立套接字,然后使用bind()确定地址和端口。接着就可以sendto()或者recvfrom()数据了。这是数据报套接口的一般过程,流式套接口与此略有差别,它需要使用listen()在指定的端口进行监听。客户端则首先要通过connect()来连接正在监听的端口。每一个连接都将被加入到等待接受(accept())的队列中。服务器调用accept()告诉对方有空闲的连接。accept()返回一个新的套接口文件描述符。原来的那个文件描述符还在继续监听指定的端口,新的描述符用于发送(send())和接受(recv())数据。
在清楚了套接字的工作原理后,开始对其进行封装,基本的目的是屏蔽一些繁琐的操作,让使用更为简单。
//ESock.h
#ifndef _ESOCK_H_
#define _ESOCK_H_
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
claes ESock
{
private:
int sin_size;
int sockfd,new_fd;
unsigned short port;
struct sockaddr_in my_addr,their_addr,get_addr;
public:
ESock();
virtual ~ESock();
int Send(const char *buf,int len);
bool Connect();
int Recv(const char *buf,int len);
void Accept();
void Listen();
bool Socket(int type);
void SetPort(unsigned short in_port);
void SetRemotePort(unsigned short in_port);
unsigned short GetRemotePort();
bool Bind();
int Recvfrom(const char *buf,int len);
int Sendto(const char *buf,int len);
bool SetRemoteAddrees(const char *addrees);
void GetRemoteAddrees(const char *addrees);
void Close();
};
#endif
//ESock.cpp
#include "esock.h"
ESock::ESock()
{
}
ESock::~ESock()
{
}
bool ESock::Socket(int type)
{
if((sockfd=socket(AF_INET,type,0))==-1)
return false;
return true;
}
void ESock::SetPort(unsigned short in_port)
{
my_addr.sin_family=AF_INET;
port=in_port;
my_addr.sin_port=htons(port);
my_addr.sin_addr.s_addr=INADDR_ANY;
}
void ESock::SetRemotePort(unsigned short in_port)
{
their_addr.sin_port=htons(in_port);
}
unsigned short ESock::GetRemotePort()
{
return ntohs(get_addr.sin_port);
}
bool ESock::Bind()
{
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1)
return false;
return true;
}
int ESock::Recvfrom(const char *buf,int len)
{
int numbytes;
int addr_len=sizeof(
4000
struct sockaddr);
if((numbytes=recvfrom(sockfd,buf,len,0,(struct sockaddr *)&get_addr,&addr_len))==-1){
return false;
}
buf[numbytes]='/0';
return numbytes;
}
int ESock::Sendto(const char *buf,int len)
{
return sendto(sockfd,buf,len,0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr));
}
bool ESock::SetRemoteAddrees(const char* addrees)
{
struct hostent *he;
if((he=gethostbyname(addrees))==NULL)
return false;
their_addr.sin_family=AF_INET;
their_addr.sin_port=htons(port);
their_addr.sin_addr=*((struct in_addr *)he->h_addr);
return true;
}
void ESock::GetRemoteAddrees(const char *addrees)
{
strcpy(addrees,inet_ntoa(get_addr.sin_addr));
}
void ESock::Close()
{
close(sockfd);
close(new_fd);
}
void ESock::Listen()
{
listen(sockfd,10);
}
void ESock::Accept()
{
sin_size=sizeof(struct sockaddr_in);
new_fd=accept(sockfd,(struct sockaddr *)&get_addr,&sin_size);
}
int ESock::Recv(const char *buf,int len)
{
return recv(new_fd,buf,len,0);
}
bool ESock::Connect()
{
if(connect(sockfd,(struct sockaddr *)&their_addr, sizeof(struct sockaddr))==-1)
return false;
return true;
}
bint ESock::Send(const char *buf, int len)
{
return send(sockfd,buf,len,0);
}
以上是极为简单的Sock封装,鉴于数据报的使用方法在此之前已有介绍,接下来只贴出用这个ESock类编写的文件传输的测试代码。
//server.cpp
#include <stdio.h>
#include "ESock.h"
#include "EFile.h"
int main()
{
ESock es;
es.Socket(SOCK_STREAM);
es.SetPort(6140);
es.Bind();
es.Listen();
es.Accept();
char buf[1000];
memset(a,'/0',1000);
EFile *fd=new EFile("accept.txt","w");
delete fd;
bool flag=true;
int i=0,recvlen=0;
while(1)
{
recvlen=es.Recv(buf,1000);
if(recvlen>0)
{
EFile *f=new EFile("accept.txt","a");
f->Write(buf,sizeof(char),recvlen);
delete f;
if(flag)
printf("Geting data……/n");
flag=false;
i++;
printf("/b/b/b/b/b/b");
printf("%6d",i);
}
}
return 0;
}
#include <stdio.h>
#include "ESock.h"
#include "EFile.h"
int main()
{
ESock es;
es.Socket(SOCK_STREAM);
printf("Input IP Address:/n");
char ip[20];
scanf("%s",ip);
es.SetRemoteAddress(ip);
es.SetRemotePort(6140);
es.Connect();
EFile file("send.txt","r");
char buf[1000];
memset(buf,'/0',1000);
int i=0;
int readlen=0;
while(readlen=file.Read(buf,sizeof(char),1000))
{
es.Send(buf,readlen);
i++;
printf("/b/b/b/b/b/b");
printf("%6d",i);
}
printf("Send OK/n");
es.Close();
return 0;
}
远方的eishn用这个程序向我传送了一个大约2M的pdf文件,我用fc将它和原始文件比较,确定传输是可靠的。这也是唯一能给我慰藉的结果。
以上所有程序均在cygwin下使用gcc编译并运行成功,最后的文件传输程序使用到了自己写的EFile类,相信各位应该能轻松将其用一般的文件读写函数来替换。所有这些都是我极为粗浅的理解和实践,恳请各位专业人士给予我的幼稚以严厉的批评指正!
fancy于2005年1月4日
网络编程大概是程序员最为热衷的技术之一,本人在无聊的课余稍稍翻阅了那本很著名的大部头《tcp/ip协议详解》。于是尝试着封装了一个极为简陋的socket类,通过编码调试,惊喜的发现它确实可以实现一些简单的传输功能。个人欣慰的同时将其发布出来,希望可以给像我一样蹩脚并挣扎于茫茫黑夜中的各位同仁一点帮助。
Socket是使用Unix文件描述符和其他程序通讯的方式。我们利用系统调用socket()。它返回套接口描述符,我们可以通过这个套接口描述符来调用send()和recv()。
Internet套接字有两种类型。一种是“Stream Socket”,流式套接口;另一种是“Datagram Socket”,数据报套接口,也叫无连接套接口。
流式套接口是可靠的双向通讯的数据流,它是无错误的传递数据的,有自己的错误控制。流式套接口的高质量数据传输是靠了“TCP”协议来实现。像qq,popo的文件传输就是使用这种方法。
数据报套接口它是不可靠的,当你发送一个数据报的时候,它可能到达,可能数据次序会颠倒。它使用的是“UDP”协议。
了解了这些,现在让我们来看一段真正的程序:
//server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 6140
#define BUFSIZE 100
main()
{
int sockfd;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
char buf[BUFSIZE];
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
printf("socket error/n");
exit(1);
}
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(MYPORT);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1){
printf("bind error");
exit(1);
}
int addr_len=sizeof(struct sockaddr);
int numbytes;
if((numbytes=recvfrom(sockfd,buf,BUFSIZE,0,(struct sockaddr *)&their_addr,&add_len))==-1){
printf("recvfrom error");
exit(1);
}
printf("Get package from %s/n",inet_ntoa(their_addr.sin_addr));
printf("Package is %d bytes/n",numbytes);
buf[numbytes]='/0';
printf("Package is /"%s/"/n",buf);
close(sockfd);
}
//client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 6140
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in their_addr;
struct hostent *he
if(argc<3){
printf("lees argument/n");
exit(1);
}
if((he=gethostbyname(argv[1]))==NULL){
printf("gethostbyname error/n");
exit(1);
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
printf("socket error/n");
exit(1);
}
their_addr.sin_family=AF_INET;
their_addr.sin_port=htons(MYPORT);
their_addr.sin_addr=*((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero),8);
int numbytes;
if((numbytes=sendto(sockfd,argv[2],strlen(argv[2],0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)))==-1){
printf("sentto error/n");
exit(1);
}
printf("Send %d bytes to %s/n",numbytes,inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
}
从上面的两段程序,我们可以看出,通过套接字发送和接受数据的步骤基本如下:
首先使用socket()建立套接字,然后使用bind()确定地址和端口。接着就可以sendto()或者recvfrom()数据了。这是数据报套接口的一般过程,流式套接口与此略有差别,它需要使用listen()在指定的端口进行监听。客户端则首先要通过connect()来连接正在监听的端口。每一个连接都将被加入到等待接受(accept())的队列中。服务器调用accept()告诉对方有空闲的连接。accept()返回一个新的套接口文件描述符。原来的那个文件描述符还在继续监听指定的端口,新的描述符用于发送(send())和接受(recv())数据。
在清楚了套接字的工作原理后,开始对其进行封装,基本的目的是屏蔽一些繁琐的操作,让使用更为简单。
//ESock.h
#ifndef _ESOCK_H_
#define _ESOCK_H_
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
claes ESock
{
private:
int sin_size;
int sockfd,new_fd;
unsigned short port;
struct sockaddr_in my_addr,their_addr,get_addr;
public:
ESock();
virtual ~ESock();
int Send(const char *buf,int len);
bool Connect();
int Recv(const char *buf,int len);
void Accept();
void Listen();
bool Socket(int type);
void SetPort(unsigned short in_port);
void SetRemotePort(unsigned short in_port);
unsigned short GetRemotePort();
bool Bind();
int Recvfrom(const char *buf,int len);
int Sendto(const char *buf,int len);
bool SetRemoteAddrees(const char *addrees);
void GetRemoteAddrees(const char *addrees);
void Close();
};
#endif
//ESock.cpp
#include "esock.h"
ESock::ESock()
{
}
ESock::~ESock()
{
}
bool ESock::Socket(int type)
{
if((sockfd=socket(AF_INET,type,0))==-1)
return false;
return true;
}
void ESock::SetPort(unsigned short in_port)
{
my_addr.sin_family=AF_INET;
port=in_port;
my_addr.sin_port=htons(port);
my_addr.sin_addr.s_addr=INADDR_ANY;
}
void ESock::SetRemotePort(unsigned short in_port)
{
their_addr.sin_port=htons(in_port);
}
unsigned short ESock::GetRemotePort()
{
return ntohs(get_addr.sin_port);
}
bool ESock::Bind()
{
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1)
return false;
return true;
}
int ESock::Recvfrom(const char *buf,int len)
{
int numbytes;
int addr_len=sizeof(
4000
struct sockaddr);
if((numbytes=recvfrom(sockfd,buf,len,0,(struct sockaddr *)&get_addr,&addr_len))==-1){
return false;
}
buf[numbytes]='/0';
return numbytes;
}
int ESock::Sendto(const char *buf,int len)
{
return sendto(sockfd,buf,len,0,(struct sockaddr *)&their_addr,sizeof(struct sockaddr));
}
bool ESock::SetRemoteAddrees(const char* addrees)
{
struct hostent *he;
if((he=gethostbyname(addrees))==NULL)
return false;
their_addr.sin_family=AF_INET;
their_addr.sin_port=htons(port);
their_addr.sin_addr=*((struct in_addr *)he->h_addr);
return true;
}
void ESock::GetRemoteAddrees(const char *addrees)
{
strcpy(addrees,inet_ntoa(get_addr.sin_addr));
}
void ESock::Close()
{
close(sockfd);
close(new_fd);
}
void ESock::Listen()
{
listen(sockfd,10);
}
void ESock::Accept()
{
sin_size=sizeof(struct sockaddr_in);
new_fd=accept(sockfd,(struct sockaddr *)&get_addr,&sin_size);
}
int ESock::Recv(const char *buf,int len)
{
return recv(new_fd,buf,len,0);
}
bool ESock::Connect()
{
if(connect(sockfd,(struct sockaddr *)&their_addr, sizeof(struct sockaddr))==-1)
return false;
return true;
}
bint ESock::Send(const char *buf, int len)
{
return send(sockfd,buf,len,0);
}
以上是极为简单的Sock封装,鉴于数据报的使用方法在此之前已有介绍,接下来只贴出用这个ESock类编写的文件传输的测试代码。
//server.cpp
#include <stdio.h>
#include "ESock.h"
#include "EFile.h"
int main()
{
ESock es;
es.Socket(SOCK_STREAM);
es.SetPort(6140);
es.Bind();
es.Listen();
es.Accept();
char buf[1000];
memset(a,'/0',1000);
EFile *fd=new EFile("accept.txt","w");
delete fd;
bool flag=true;
int i=0,recvlen=0;
while(1)
{
recvlen=es.Recv(buf,1000);
if(recvlen>0)
{
EFile *f=new EFile("accept.txt","a");
f->Write(buf,sizeof(char),recvlen);
delete f;
if(flag)
printf("Geting data……/n");
flag=false;
i++;
printf("/b/b/b/b/b/b");
printf("%6d",i);
}
}
return 0;
}
#include <stdio.h>
#include "ESock.h"
#include "EFile.h"
int main()
{
ESock es;
es.Socket(SOCK_STREAM);
printf("Input IP Address:/n");
char ip[20];
scanf("%s",ip);
es.SetRemoteAddress(ip);
es.SetRemotePort(6140);
es.Connect();
EFile file("send.txt","r");
char buf[1000];
memset(buf,'/0',1000);
int i=0;
int readlen=0;
while(readlen=file.Read(buf,sizeof(char),1000))
{
es.Send(buf,readlen);
i++;
printf("/b/b/b/b/b/b");
printf("%6d",i);
}
printf("Send OK/n");
es.Close();
return 0;
}
远方的eishn用这个程序向我传送了一个大约2M的pdf文件,我用fc将它和原始文件比较,确定传输是可靠的。这也是唯一能给我慰藉的结果。
以上所有程序均在cygwin下使用gcc编译并运行成功,最后的文件传输程序使用到了自己写的EFile类,相信各位应该能轻松将其用一般的文件读写函数来替换。所有这些都是我极为粗浅的理解和实践,恳请各位专业人士给予我的幼稚以严厉的批评指正!
相关文章推荐
- 建立一个最简单的socket类——网络编程初步
- 图书馆惊魂记之一(一个简单的领域模型的建立过程)
- 图书馆惊魂记之一(一个简单的领域模型的建立过程)
- [BizTalk][Adapter][部署]BTS学习笔记1:建立一个简单的Biztalk数据交换项目(一)
- java建立一个简单的图片浏览器
- 利用nodejs建立一个简单的聊天服务器
- 用pureftpd+pureDB虚拟用户,建立一个简单安全(不需要数据库支持)的linux ftp站
- 建立一个简单的 struts2 + json 应用
- 新闻发布程序(1):建立一个通过前台输入文本内容的简单网页程序
- 例 9.8 建立一个如图9.9所示的简单链表,它由3个学生数据的结点组成,要求输出各结点中的数据。
- 使用div建立一个简单的前端页面
- django开发项目实例1--建立一个项目并初步运行
- 建立一个简单的android涂鸦工程
- WCF学习笔记(二)—— 建立一个简单的WCF应用程序
- 简单的建立一个网络电视直播软件
- 建立一个简单的servlet controller
- 用VMM在Hyper-V类型host上建立一个简单的domain
- SQL Server 2005 Integration Services (SSIS) (4) - 建立一个简单的SSIS Package
- 建立一个最简单的项目,实践cobertura在maven中应用
- 第八周项目-建立一个简单的学生信息表