Linux网络编程3——socket,linux网络编程


宏定义

首先介绍两个宏定义,看如下代码

代码1

/*************************************************************************
  > File Name: test.c
  > Author: KrisChou
  > Mail:zhoujx0219@163.com 
  > Created Time: Thu 28 Aug 2014 10:11:59 AM CST
 ************************************************************************/

#include <stdio.h>
#define __JOIN__(post_fix) tag_##post_fix

typedef int tag_element;
typedef int tag_arr[10];

int main(int argc, char *argv[])
{
    __JOIN__(element) val1; 
    __JOIN__(arr) val2;

    int index;
    for(index = 0; index < 9; index++)
    {
        val2[index] = index + 1;
        printf("%3d", val2[index]);
    }
    printf("\n");
    return 0;
}

运行结果:

[purple@localhost 0828]$ gcc test.c
[purple@localhost 0828]$ ./a.out
  1  2  3  4  5  6  7  8  9

看一下预处理后的代码

typedef int tag_element;
typedef int tag_arr[10];

int main(int argc, char *argv[])
{
    tag_element val1;
    tag_arr val2;

   int index;
   for(index = 0; index < 9; index++)
   {
     val2[index] = index + 1;
     printf("%3d", val2[index]);
   }
   printf("\n");
   return 0;
}

即宏定义#define __JOIN__(post_fix) tag_##post_fix  将tag_与参数post_fix连接起来了

举例

观察结构体struct sockaddr_in的某个参数,之前有宏定义如下:

#define __SOCKADDR_COMMON(sa_prefix) sa_family_t sa_prefix##family
那么有:
__SOCKADDR_COMMON (sin_);     -->     sa_family_t sin_family

代码2

#include <stdio.h>
#define STR(s) #s
//#define IP 192.168.1.1

int main(int argc, char *argv[])
{
    char buf[] = STR(helloworld!);
    printf("%s \n", buf);
    return 0;
}

运行结果

[purple@localhost 0828]$ gcc test1.c
[purple@localhost 0828]$ ./a.out
helloworld!

小结

我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起。

使用TCP协议

服务器端

1. 头文件

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

2. socket函数:生成一个套接口描述符。

原型: int socket(int domain,int type,int protocol);
参数: domainAF_INET: Ipv4网络协议;AF_INET6:IPv6网络协议。 type  tcp:SOCK_STREAM;udp:SOCK_DGRAM。protocol指定socket所使用的传输协议编号。通常为0。
 
返回值:成功则返回套接口描述符,失败返回-1
 

3. bind函数:用来绑定一个端口号和IP地址,使套接口与指定的端口号和IP地址相关联。

原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);

参数:sockfd 为前面socket的返回值。my_addr 为结构体指针变量 addrlen 为sockaddr的结构体长度。通常是计算sizeof(struct sockaddr)

返回值:成功则返回0,失败返回-1

对于不同的socket domain定义了一个通用的数据结构,如下。注意,此sockaddr结构会因使用不同的socket domain而有不同结构定义。
struct sockaddr  //此结构体不常用
{
    unsigned short int sa_family;  //调用socket()时的domain参数,即AF_INET值。
    char sa_data[14];              //最多使用14个字符长度
};

例如使用AF_INET domain,其socketaddr结构定义如下:

struct sockaddr_in  //常用的结构体
{
    unsigned short int sin_family;  //即为sa_family AF_INET
    uint16_t sin_port;              //为使用的port编号
    struct in_addr sin_addr;        //为IP 地址
     unsigned char sin_zero[8];      //未使用
};

其中,struct in_addr 为:

struct in_addr
{
    uint32_t s_addr;
};

4. listen函数:使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。

原型:int listen(int sockfd,int backlog);
 
参数:sockfd为前面socket的返回值,即sfd。backlog指定同时能处理的最大连接要求,通常为10或者5,最大值可设至128。
 
返回值:成功则返回0,失败返回-1

5. accept函数:接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求。(也就是说,类似于移动营业厅,如果有客户打电话给10086,此时服务器就会请求连接,处理一些事务之后,就通知一个话务员接听客户的电话,也就是说,后面的所有操作,此时已经于服务器没有关系,而是话务员跟客户的交流。对应过来,客户请求连接我们的服务器,我们服务器先做了一些绑定和监听等等操作之后,如果允许连接,则调用accept函数产生一个新的套接字,然后用这个新的套接字跟我们的客户进行收发数据。也就是说,服务器跟一个客户端连接成功,会有两个套接字。)

原型:int accept(int s,struct sockaddr * addr,int * addrlen);
 
参数:s为前面socket的返回值,即sfd。addr为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。addrlen表示结构体的长度,为整型指针。    
 
返回值:成功则返回新的socket描述符,用于与客户端通信。失败返回-1

6. recv函数:用新的套接字来接收远端主机传来的数据,并把数据存到由参数buf 指向的内存空间。

原型:int recv(int sockfd,void *buf,int len,unsigned int flags);
 
参数:sockfd为前面accept的返回值,即new_fd,也就是新的套接字。buf表示缓冲区。len表示缓冲区的长度。flags通常为0。
 
返回值:成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1。

7. send函数:用新的套接字发送数据给指定的远端主机。

原型:int send(int s,const void * msg,int len,unsigned int flags);
 
参数:s为前面accept的返回值,即new_fd。msg一般为常量字符串。len表示长度。flags通常为0。
 
返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1。

8. close函数:当使用完文件后若已不再需要则可使用close()关闭该文件,并且close()会让数据写回磁盘,并释放该文件所占用的资源。

原型:int close(int fd);
 
返回值:若文件顺利关闭则返回0,发生错误时返回-1。
 

小结

1. listen函数使得主动连接套接口变为被动连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。listen函数在一般在调用bind之后,调用accept之前调用,其函数原型如下:

int listen(int sockfd, int backlog)

2. socket函数返回的套接字fd,默认是一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认一个套接字是主动连接的,所以需要通过某种方式来告诉系统,而用户进程正是通过listen函数来完成这件事。

3. 当服务器进程处理连接请求时,可能同时还存在其它的连接请求。内核会在自己的进程空间里维护一个队列(客户连接请求队列)用于存放服务器监听到的连接请求的联系方式(端口号与IP)。

4. 服务程序调用accept函数从处于监听状态的流套接字的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回 INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。

客户端

connect函数:用来请求连接远程服务器,将参数sockfd 的socket 连至参数serv_addr 指定的服务器IP和端口号上去。

原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
 
参数:sockfd为前面socket的返回值,即sfd。serv_addr为结构体指针变量,存储着远程服务器的IP与端口号信息。addrlen表示结构体变量的长度。
 
返回值:成功则返回0,失败返回-1。
 

linux socket网络编程代码

Linux是多任务的操作系统,可在运行在Intel 80386及更高档次的PC机、ARMS、MIPS和PowerPC等多种计算机平台,已成为应用广泛、可靠性高、功能强大的计算机操作系统,Linux具有内核小、效率高、源代码开放等优点,还内含了TCP/IP网络协议,很适合在服务器领域使用,而服务器主要用途之一就是进行网络通信,随着计算机办公自动化处理技术的应用与推广,网络的不断普及,传统的纸张式文件传输方式已经不再适合发展的需要,人们更期待一种便捷、高效、环保、安全的网络传输方式.

协议概述TCP/IP即传输控制协议/网络协议[1](Transmission Control Protocol/Internet Protocol),是一个由多种协议组成的协议族,他定义了计算机通过网络互相通信及协议族各层次之间通信的规范,图1描述了Linux对IP协议族的实现机制[2]。

Linux支持BSD的套接字和全部的TCP/IP协议,是通过网络协议将其视为一组相连的软件层来实现的,BSD套接字(BSD Socket)由通用的套接字管理软件支持,该软件是INET套接字层,用来管理基于IP的TCP与UDP端口到端口的互联问题,从协议分层来看,IP是网络层协议,TCP是一个可靠的端口到端口的传输层协议,他是利用IP层进行传接报文的,同时也是面向连接的,通过建立一条虚拟电路在不同的网路间传输报文,保证所传输报文的无丢失性和无重复性。用户数据报文协议(User Datagram Protocol,UDP)也是利用IP层传输报文,但他是一个非面向连接的传输层协议,利用IP层传输报文时,当目的方网际协议层收到IP报文后,必须识别出该报文所使用的上层协议(即传输层协议),因此,在IP报头上中,设有一个"协议"域(Protocol)。通过该域的值,即可判明其上层协议类型,传输层与网络层在功能说的最大区别是前者提供进程通信能力,而后者则不能,在进程通信的意义上,网络通信的最终地址不仅仅是主机地址,还包括可以描述进程的某种标识符,为此,TCP/UDP提出了协议端口(Protocol Port)的概念,用于标识通信的进程,例如,Web服务器进程通常使用端口80,在/etc/services文件中有这些注册了的端口地址。

对于TCP传输,传输节点间先要建立连接,然后通过该连接传输已排好序的报文,以保证传输的正确性,IP层中的代码用于实现网际协议,这些代码将IP头增加到传输数据中,同时也把收到的IP报文正确的传送到TCP层或UDP层。TCP是一个面向连接协议,而UDP则是一个非面向连接协议,当一个UDP报文发送出去后,Linux并不知道也不去关心他是否成功地到达了目的的主机,IP层之下,是支持所有Linux网络应用的网络设备层,例如点到点协议(Point to Point Protocol,PPP)和以太网层。网络设备并非总代表物理设备,其中有一些(例如回送设备)则是纯粹的软件设备,网络设备与标准的Linux设备不同,他们不是通过Mknod命令创建的,必须是底层软件找到并进行了初始化之后,这些设备才被创建并可用。因此只有当启动了正确设置的以太网设备驱动程序的内核后,才会有/dev/eth0文件,ARP协议位于IP层和支持地址解析的协议层之间。

网络通信原理所有的网络通信就其实现技术可以分为两种,线路交换和包交换,计算机网络一般采用包交换,TCP使用了包交换通信技术,......余下全文>>
 

比较Linux与Windows在网络编程方面的特点

找了一段,大致涉及到了您的问题:

一、socket的模式
socket一般有两种模式:同步和异步(windows网络编程技术中也可叫锁定和非锁定,Linux网络编程叫阻塞和非阻塞)。

二、socket的类型

socket一般有三种类型,基于TCP的流式套接字,基于UDP的数据报套接字和原始套接字。

三、socket的IO模型

socket
的IO模型是编程中使用socket两种模式的策略,它们适用的场合不同,在不同的操作系统上支持的模型也不同,例如windows从NT版本才开始支持
完成端口模型。Linux和Windows所支持的模型也有区别,当然也有相同的地方,可能叫法不一样,但大致思路是一样的,下面分别介绍windows
和Linux的IO模型

1、 Windows下的套接字IO模型:

A、 Select(选择)模型
用于同步socket的状态检测模型,又叫(Linux)多路复用,可以同时检测多个socket的状态

B、 WSAAsyncSelect(异步选择)模型
用于异步socket的异步事件设置,它是基于Windows消息的模型,必须先打开一个窗口,然后把窗口和socket的消息绑定,这样,在socket有消息通知时,操作系统便通知窗口,然后在窗口进行处理。

C、 WSAEventSelect(异步事件)模型

于异步socket的异步事件,它是基于网络事件的模型,先使用CreateEvent创建一个事件,然后使用WSAEventSelect进行事件绑
定,然后可以使用WaitForMultipleObject(Event)进行事件监听,可以同时监听多个事件,不光是socket的,比如可以监听使
用CreateWaitableTimer创建的Timer等。

D、 重叠IO模型

于异步socket,在创建socket时需要在创建函数WSASocket中使用WSA_FLAG_OVERLAPPED标志,然后在投递IO请求的时
候将一个Overlapped结构体指针赋给投递函数,可以使用WSAWaitForMultipleObject来监听事件,然后使用
WSAGetOverlappedResult来获取IO的状态,也可以在Overlapped结构体中使用完成例程来处理,即在投递函数中把完成例程赋
给投递函数。

E、 完成端口模型

是迄今为止最复杂的一种IO模型,当应用程序需要管理众多的套接字并且希望随着系统内安装的CPU数目的增多,应用程序的性能也可以线性增加,就可以使用
这种模型,它的原理是每个CPU可以单独负责一个线程的执行,避免线程的频繁切换。使用这种模型往往可以达到最佳的系统性能。

先需要使用CreateIOCompletePort来创建完成端口,然后将IO句柄和此端口绑定,绑定也是使用此函数,当然也可以一次完成。接着是创建
工作者线程,工作者线程会使用GetQueuedCompletionStatus进入完成端口维护的线程池,当有完成事件时,会激活一个线程。

2、 Linux下的IO模型

A、阻塞IO

B、非阻塞IO

C、IO多路复用(选择)

D、信号驱动
用于异步socket,首先设定信号处理函数,然后使用fcntl函数设定socket的拥有者,像windows下使用WSAAsncSelect设定socket的窗口一样。使用这种模型,当内核操作可以被操作的时候通知我们的应用程序

E、异步IO
当内核在所有操作完成后才会通知应用程序

四、......余下全文>>
 

相关内容