分类: C/C++
2007-07-13 09:45:35
当然,我们现在将要开始编写的第一个网络程序,虽然非常简单,但是却可以很清楚的说明大部分编写网络程序需要的基本概念,好了先让我们看看网络程序的TCP服务器端的编写步骤:
1. 首先,你需要创建一个用于通讯的套接口,一般使用socket调用来实现。这等于你有了一个用于通讯的电话:)
2.
然后,你需要给你的套接口设定端口,相当于,你有了电话号码。这一步 一般通过设置网络套接口地址和调用bind函数来实现。
3.
调用listen函数使你的套接口成为一个监听套接字。 以上三个步骤是TCP服务器的常用步骤。
4.
调用accept函数来启动你的套接字,这时你的程序就可以等待客户端的连接了。
5. 处理客户端的连接请求。
6. 终止连接。
现在让我们来看看网络程序客户端的编程步骤:
1. 和服务器的步骤一样。
2.
通过设置套接口地址结构,我们说明,客户端要与之通讯的服务器的IP地址和端口。
3. 调用connect函数来连接服务器。
4.
发送或者接收数据,一般使用send和recv函数调用来实现。
5. 终止连接。
以上的步骤,是比较普遍的,我们可以从中看出,编写网络程序是很有意思的,同时,也不是非常复杂,当然,这不包括,高难度的东西:-),下次,我会给出一个简单的服务器和一个客户机程序。
今天,我给出的代码包括一个服务器,和一个客户机程序,但是省略了很多代码,
比如说错误处理代码,这样做主要是为了使网络编程的主线清楚,所以,这两个程序谈不上网络安全性,和稳定性,这些是以后的话题。但是对于基本理解
网络编程已经足够了。我会在下次给出一个完整可运行的程序。下面我会详细 解释程序的步骤:
先要包括一部分网络编程必须的头部文件:
#include
#include
#include
#include
int main(int
argc,char *argv[])
{
int listensock,connsock;
/定义两个socket套接字,一个用于监听,一个用于通讯
struct sockaddr_in serveraddr; /定义网络套接字地址结构
const char buff[] = "Hello! Welcome here!\r\n"; /定义要发送的数据缓冲区;
listensock
= socket(AF_INET,SOCK_STREAM,0); /创建一个套接字,用于监听
bzero(&serveraddr,sizeof(servaddr)); /地址结构清零
serveraddr.sin_family =
AF_INET; /指定使用的通讯协议族
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
/指定接受任何连接,因为服务器不知道谁会要求连接
serveraddr.sin_port = htons(5000); /指定监听的端口
bind(listensock,(sockaddr *)&serveraddr,sizeof(serveraddr)); /给套接口邦定地址
listen(listensock,1024); /开始监听
connsock =
accept(listensock,(sockaddr *)NULL, NULL);
/建立通讯的套接字,accept函数,等待客户端程序使用connect函数的连接
send(connsock,buff,sizeof(buff), 0); /向客户端发送数据
close(connsock);
/关闭通讯套接字
close(listensock); /关闭监听套接字
}
这是客户端的程序:
int
main(int argc,char **argv)
{
char recvbuff[100]; /定义要接收的数据缓冲区
int
sockfd; /定义一个socket套接字,用于通讯
struct sockaddr_in serveraddr;/定义网络套接字地址结构
if(argc != 2){
printf("Usage: echo ip地址");
exit(0);
}
sockfd = socket(AF_INET,SOCK_STREAM,0); /创建一个套接字
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family =
AF_INET; /指定使用的通讯协议族
serveraddr.sin_port = htons(5000);/指定要连接的服务器的端口
inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);
connect(sockfd,(sockaddr *)&serveraddr,sizeof(serveraddr)); /连接服务器
recv(sockfd,recvbuff,sizeof(recvbuff),0); /接收服务器的数据
printf("%s\n",recvbuff); /打印接收到的数据
close(sockfd); /关闭套接字
}
这两个程序运行后,当客户端连接到服务器后,将接收到服务器传来的字符串Hello! Welcome
here!,不过,程序调试的任务还得又你自己要完成。
你想知道著名的oicq使用的是什么技术来收发消息的吗?今天,我给出的两个程序,一个服务器
和一个客户端程序,便能告诉你,其中的基本技术,当然,我指的不包括,它的界面是怎样做的:)
这两个程序使用的是UDP套接字编程,上一次给出的其实是使用TCP套接字编程,你自己可以先分析
一下,他们的异同点,我会在下一次,分析这两种编程的区别。
这是发送数据的程序:
/*头部文件包括网络需要的和基本输入输出需要的*/
#include #include
#include
#include
#include
int port = 5500;
void main()
{
int sockfd;
int count = 0;
int flag;
char buf[80];
struct sockaddr_in address;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
//看到不同的地方了吗?
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr =
inet_addr("127.0.0.1");
address.sin_port = htons(port);
flag = 1;
do{
sprintf(buf,"Packet %d\n", count);
if(count > 30){
sprintf(buf,"over\n");
flag = 0;
}
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&address,
sizeof(address)); // 发现了吗使用的函数不一样,它也是发送数据
count++;
}while (flag);
}
这是接收数据的程序:
#include
#include
#include
#include
#include
char *hostname = "127.0.0.1"; //这个特殊的ip表示本的计算机
void
main()
{
int sinlen;
int port = 8080;
char message[256];
int sockfd;
struct sockaddr_in sin;
struct hostent
*server_host_name; // hostent结构有着机器的名字等信息
server_host_name =
gethostbyname("127.0.0.1"); // 这个函数用来得到“127.0.0.1”的主机名字,也就是本机的名字
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(port);
sockfd = socket(PF_INET,SOCK_DGRAM,0); //这里也不一样
bind(sockfd,(struct
sockaddr *)&sin,sizeof(sin));
while(1){
sinlen =
sizeof(sin);
recvfrom(sockfd,message,256,0, (struct sockaddr
*)&sin,&sinlen);// 它是接受数据的函数
printf("\nData come from
server:\n%s\n",message);
if(strncmp(message,"over",4) ==0)break;
}
close(sockfd);
}
当你编译调试通过了后,在一个窗口,运行接收程序,一个窗口运行发送程序,你就可以看到数据被创送了。网络程序是可以在本地机器上,使用两个不同的窗口来运行,模拟客户端和服务器的
今天,笔者会解释网络编程中非常重要的两个概念:TCP编程和UDP编程,这是真真进入网络编程的灿烂世界必须深入理解的部分。
首先,我们必须明白,一般操作系统包括Windows,Linux,UNIX,他们提供的供应用程序员使用的编程接口,一般的函数名字都差不
多。不同的是,他们的操作系统对这些函数(也可以说是系统调用)的实现细节不尽相同,因此各种操作系统,在提供网络服务的时候,就存在着诸如速度、效率及
稳定性的差别。
那么,就网络编程的套接口字的选则来看,一样存在着以上的差别。也就是说,你选择TCP套接字和选择UDP编程,在传输数据时,一样有着速度、效率及稳定性的差别。首先,明白这一点,对于开始网络编程是非常重要的。
TCP套接字,操作系统向你提供的是一个稳定的数据通路,从程序员的角度来看,你只需要明白,当你使用TCP编程时,如果你调用
的发送数据函数,比如send()函数,它的返回成功,那么表示,系统发送出的数据肯定被通讯的对方准确接收到了。而UDP套接字,操作系统给你提供的是
一个不稳定的,无连接的数据通路,所以当你使用UDP编程时,如果你调用的发送数据的函数,如sendto()
函数,它的返回成功,那么表示,你要发送的数据已经发送到了网上,而这些数据是否到达了你要发送数据的对方,那时不一定的。所以对于UDP编程,我们如果
要保证数据的发送的准确、及时,我们需要自己建立起,一些数据传送的控制机制,来确保我们的数据成功发送到对方,而不仅仅是把数据发送到了网络上,我们就
不过问了。当然对于TCP编程,操作系统已经帮我们做了一系列控制功能,所以我们不需要考虑太多的东西;-)
从以上的分析,你应该可以看出,对于初学者或者说,TCP编程是非常好的入门点,也是很容易的。当然,UDP有它自己的好处,不过它虽然
不能保证你调用一次发送函数就把数据发送到对方那里,但是只要你进行适当的处理,你会发现,UDP发送数据的速度,比TCP编程要快!天下没有十全十美
的!
TCP编程拥有了可靠的数据连接,UDP不具有,但是在速度方面,UDP编程缺优于TCP编程,特别是对于传输短消息,所以我认为OICQ所以选择UDP编程,这个原因是其中很重要的一个^-^。
现在我已经说明了TCP和UDP编程的重要差别,虽然这些差别是由协议本身引入的,但是对于编程来说,理解了是很有好处的。
当然,那对于一个程序员来说还的明白,选择TCP编程,和UDP编程到底是怎样体现在代码上的,其实如果你仔细分析过我前面给出的两个程序,你也许已经明白,除了他们使用的发送数据和接收数据的函数不怎样一样之外,最重要的差别在于当你使用socket()函数
建立套接字的时候,你需要的指定的三个参数中,中间的那个参数。如果你要使用TCP编程,你要使用SOCK_STREAM,而如果你要使用UDP编程,使用SOCK_DGRAM,关于TCP和UDP的更加深入的区别和介绍,我会在以后讲述。
下次,我会给大家讲述一下,服务器和客户机的概念和区别:)
服务器/客户机模式是网络通讯交互的最常用模式,我们必须要深刻的理解这种交互模式。
其实,网络软件在很大程度上是对各种网络协议的实现,不管这种协议是官方的,还是你自己定义的。所以,网络软件的好坏也和协议的好坏有着直接的关系。当然,服务器/客户机模式在某种程度上就规定了这样一种机制:
1. 服务器方的程序首先启动,开始等待。
2. 客户机的程序启动,向服务器提出通讯连接的请求。
3.
服务器决定是否接受客户机的连接请求。并且,给客户机一个回答。
4.
如果,服务器和客户机建立了连接,通常的协议,会给出那一方应该在这个时候首先发送数据,还有发送数据的内容,格式等等。
5.
这样,一方发送,另一方接受,然后回答。
服务器和客户机就是在这样的一来一往的情况下,进行通讯的,但是为什么要选择这种依次发送的顺序了,这些都是因为要解决,在网络上传输数据
时,可能出现的死锁和饿死等现象。
对于这两种现象,我们可以这样理解,比如,我向你发了消息,等你的回答,然后才进行下一步,可是这时候,你可能也在等待我的数据到来,然后发回答给我,可
是问题出现了,如果你没有得到我的消息(掉了包),那么我和你可能一直等待下去。当然,常用的各种协议,
比如SMTP,POP3,FTP,WWW都很好的定义了发送和接收数据的顺序问题。这也是我们要很好理解的。刚才我说的那个例子,其实就是一种死锁,两方
都不能继续通讯了。至于饿死现象,我们可以这样想,一个服务器每次只能处理一个用户的通讯请求,那么当他和一个客户机通讯时,他接收了客户机的连接请求
后,等待客户机的数据发送过来,可客户机的数据迟迟不到(可能失掉了,可能客户机更本没发送数据),这种情况下,服务器将不能和别的客户机通讯,资源都被
这个客户机用了,那么对于其他的客户机用户,就处于一种饿死状态。
我们当然可以通过,规定很多的东西来保证我们通讯的顺利进行。比如一方发送,一方等待。
发送方在没有得到回答前重复多次发送数据。发送方还可以使用定时器等方法,来保证不出现饿死和死锁现象。如果你想学习更多的方法和思想,你可以学学TCP/IP协议和各种应用协议,他们在不同的层次上解决了各种可能遇到的问题。
当然,在网络上会出现的障碍是多种多样的,你所用的协议及你所写的代码,一起决定了你的网络程序的性能和安全。所以,现实生活中的网络程序往往是很复杂的.