Linux网络编程入门小例子

        如果你想进入Linux神奇的网络编程世界,请跟笔者来,在学习之前,笔者只需要你拥有一定的C语言编程知识,就足够了。笔者会讲述编写网络程序需要的基本知识。
  
        所谓网络,在软件人员的观点来看,就是很多的用物理链路(比如,以太网,无线网络)连在一起的计算机,并且安装有网络程序。就像打电话,我们需要知道对方的号码一样,网络程序也需要知道要和那台计算机通讯,在这里,就是计算机的网络接口所拥有的IP地址。其实,一个完整的网络程序拥有两个独立的程序,他们分别运行在两个计算机上,一个是网络通讯的服务器端,一个是网络通讯的客户端,就像打电话,需要一个打电话的,还要一个接电话的是一样的,所以,我们需要编写两个程序,才是完整的网络通讯程序,我们熟悉的OutLook,FOXMAIL等都是网络程序中的客户端程序,而Apache,QMail等便是服务器端程序。只是往往,网络程序的客户端和服务器端之间有一定的通讯交互协议,比如SMTP,POP3,HTTP,FTP等,对于我们网络程序的编写者来说,他们就规定了通用的交互协议,这些协议规定了客户端,服务器端应该完成的工作,所以,编写好网络程序还需要理解好协议,不过,一般说来,我们用不着自己写协议,有很多的协议,我们可以使用的,都有RFC文档来说明了,你可以在网络上找到各种协议的RFC.

  当然,我们现在将要开始编写的第一个网络程序,虽然非常简单,但是却可以很清楚的说明大部分编写网络程序需要的基本概念,好了先让我们看看网络程序的TCP服务器端的编写步骤:
1. 首先,你需要创建一个用于通讯的套接口,一般使用socket调用来实现。这等于你有了一个用于通讯的电话:)
2. 然后,你需要给你的套接口设定端口,相当于,你有了电话号码。这一步 一般通过设置网络套接口地址和调用bind函数来实现。
3. 调用listen函数使你的套接口成为一个监听套接字。 以上三个步骤是TCP服务器的常用步骤。
4. 调用accept函数来启动你的套接字,这时你的程序就可以等待客户端的连接了。
5. 处理客户端的连接请求。
6. 终止连接。
        现在让我们来看看网络程序客户端的编程步骤:
1. 和服务器的步骤一样。
2. 通过设置套接口地址结构,我们说明,客户端要与之通讯的服务器的IP地址和端口。
3. 调用connect函数来连接服务器。
4. 发送或者接收数据,一般使用send和recv函数调用来实现。
5. 终止连接。
  以上的步骤,是比较普遍的,我们可以从中看出,编写网络程序是很有意思的,同时,也不是非常复杂,当然,这不包括,高难度的东西:-),下次,我会给出一个简单的服务器和一个客户机程序。
今天,我给出的代码包括一个服务器,和一个客户机程序,但是省略了很多代码, 比如说错误处理代码,这样做主要是为了使网络编程的主线清楚,所以,这两个程序谈不上网络安全性,和稳定性,这些是以后的话题。但是对于基本理解 网络编程已经足够了。我会在下次给出一个完整可运行的程序。下面我会详细 解释程序的步骤:

先要包括一部分网络编程必须的头部文件,这是服务端程序:
tcp_server.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> 
#define MAXLINE 4096
#define PORT 5001

int main(int argc,char *argv[])
{
	//定义两个socket套接字,一个用于监听,一个用于通讯
	int listensock, connsock, n; 
	struct sockaddr_in serveraddr; //定义网络套接字地址结构
	char buff[4096];
	const char sendbuff[] = "Hello! Welcome here!\r\n"; //定义要发送的数据缓冲区;

	listensock = socket(AF_INET,SOCK_STREAM,0); //创建一个套接字,用于监听
	bzero(&serveraddr, sizeof(serveraddr)); //地址结构清零
	serveraddr.sin_family = AF_INET; //指定使用的通讯协议族
	//指定接受任何连接,因为服务器不知道谁会要求连接
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 
	serveraddr.sin_port = htons(PORT); //指定监听的端口

	bind(listensock, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); //给套接口邦定地址
	listen(listensock, 1024); //开始监听

    while(1) { 
		//建立通讯的套接字,accept函数,等待客户端程序使用connect函数的连接
        if ((connsock = accept(listensock, (struct sockaddr*)NULL, NULL)) == -1) { 
            printf("accept socket error: %s(errno: %d)",strerror(errno), errno); 
            continue; 
        }
        n = recv(connsock, buff, MAXLINE, 0);
        buff[n] = '\0';
        printf("recv msg from client: %s\n", buff);
		if (strncmp(buff, "sleep", 5) == 0) {
			int sec = atoi(buff+5);
			printf("sleep %d second\n", sec);
			if (sec > 0) {
				sleep(sec);
			}
		}
		send(connsock, buff, sizeof(buff), 0); //向客户端发送数据
        printf("send msg to client: %s\n", buff);
		close(connsock); //关闭通讯套接字
        printf("\n");
    }

	close(listensock); //关闭监听套接字
}

编译命令:gcc tcp_server.c -o server

执行命令:./server

这是客户端的程序:
tcp_client.c:

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <arpa/inet.h>
#include <netinet/in.h> 
#define MAXLINE 4096
#define PORT 5001

int main(int argc, char** argv) 
{ 
    int sockfd, n; 
    char recvline[4096], sendline[4096]; 
    struct sockaddr_in servaddr; 
    if (argc < 2) { 
        printf("usage: ./client <ipaddress>\n"); 
        return 1;
    } 
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        return 1;
    }
    memset(&servaddr, 0, sizeof(servaddr)); 
    servaddr.sin_family = AF_INET; 
    servaddr.sin_port = htons(PORT); 
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { 
        printf("inet_pton error for %s\n",argv[1]); 
        return 1;
    } 
    if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { 
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno); 
        return 1;
    } 
    if (argc == 3) {
        memcpy(sendline, argv[2], sizeof(argv[2]));
    } else {
        reinput:
        printf("input send msg to server: \n");
        fgets(sendline, 4096, stdin);
        //删除换行
        sendline[strlen(sendline)-1] = '\0';
        if (strlen(sendline) == 0) {
            goto reinput;
        }
    }
    if (send(sockfd, sendline, strlen(sendline), 0) < 0) { 
        printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
        return 1;
    }
    if (recv(sockfd, recvline, sizeof(recvline), 0) < 0) {
        printf("recv msg error: %s(errno: %d)\n", strerror(errno), errno);
        return 1;
    }
    printf("recv msg from server: %s\n", recvline);
    close(sockfd);
    return 0;
}

编译命令:gcc tcp_client.c -o client

执行命令:./client 127.0.0.1 或 ./client 127.0.0.1 sleep10 或 ./client 127.0.0.1 mymsg


这两个程序运行后,当客户端连接到服务器后,将接收到服务器传来的字符串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协议和各种应用协议,他们在不同的层次上解决了各种可能遇到的问题。
  当然,在网络上会出现的障碍是多种多样的,你所用的协议及你所写的代码,一起决定了你的网络程序的性能和安全。所以,现实生活中的网络程序往往是很复杂的.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值