C网络编程

网络编程

  编辑

网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是 数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、 服务器架设和 网页设计这5部分你都要接触。

静态代码

静态代码是 服务器不解析直接发送给客户端的部分,用做布局效果,一般不用于数据库操作
静态代码分 htmljavascriptcss等,其中 [1]    html语言是基础,要学网络编程就先学html语言.javascript用于实现某些特效,css是样式语言.这3个语言组合起来,可以设计出美妙的 网页效果

动态代码

动态代码是 服务器需要解析的部分,用作数据库连接操作等.有 php,jsp, asp这几种语言你只用先学一种就可.如果是想快速入门,建议先学asp,如果想学了找工作等,建议学php或jsp,
开发工具有很多种,我推荐一种,网络3剑客,其中dw是开发代码的,fw是做图的.flash是做动画的.
数据库要结合你学的 动态语言来选择,asp系列的,你可以使用access,大型点使用mySQL.
php和mySQL是很好的搭档.
服务器架设也是结合你学的动态语言的,windows下安装 iis很方便,iis可以运行asp,安装 .net框架后能运行,这两者架设相对简单,也是我推荐你入门学 asp的原因. php一般安装apache服务器,jsp一般安装 tomcat服务器.只有架设好服务器,才能浏览动态语言编写的程序.
虽然是编程,但是总会涉及到 网页设计部分,还是要去学学怎么简单的作图和动画。







套接字地址

  • Linux系统的套接字可以支持多种协议,每种不同的协议都是用不同的地址结构。
  • 在头文件中定义了一个通用套接字地址结构sockaddr:
struct sockaddr
{
    unsigned short sa_family; //16位 套接字的协议簇地址类型,AF_XX
    char        sa_data[14];//14字节 存储具体的协议地址
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 为了处理struct sockaddr,程序员创造了一个并列的大小相同结构:struct sockaddr_in(“in”代表”Internet”。)
  • struct sockaddr_in在/usr/include/netinet/in.h中定义:
struct sockaddr_in
{
    unsigned short sin_len;  //IPv4地址长度
    short int sin_family;   //16位 指代协议簇,TCP套接字编程为AF_INET
    unsigned short sin_port;     //16位端口号(使用网络字节顺序),数据类型是一个16位的无符号整数
    struct in_addr sin_addr;   //32位,存储IP地址,是一个in_addr结构体
    unsigned char sin_zero[8];     //8字节,为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
};
struct in_addr //32 位
{
    unsigned long s_addr;  //按照网络字节顺序存储IP地址
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 填充特定协议地址时使用sockaddr_in
  • 作为bind()、connect()、sendto()、recvfrom()等函数的参数时需要使用sockaddr,
  • 这时要通过指针强制转换的方式转为struct sockaddr 指针。

IPv4地址结构示例

struct sockaddr_in mysock;
mysock.sin_family = AF_INET;  //TCP地址结构
mysock.sin_port = htons(3333); //字节顺序转换函数
mysock.sin_addr.s_addr = inet_addr("166.111.160.10"); //设置IP地址
//如果mysock.sin_addr.s_addr = INADDR_ANY,则不指定IP地址(用于server程序)
bzero(&(mysock.sin_zero),8); //设置sin_zero为8位保留字节
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

IPV6套接字地址结构sockaddr_in6

#DEFINE SIN6_LEN
struct sockaddr_in6
{
    unsigned short  sin6_len;        //16位 IPv6地址长度,是一个无符号的8位整数,表示128位的IPv6地址
    short int   sin6_family;   //16位 地址类型为AF_INET6
    unsigned short  sin6_port;     //16位 存储端口号,使用网络字节顺序
    unsigned short int sin6_flowinfo;  //低24位是流量标号,然后是4位优先级标志,剩下4位保留
    struct in6_addr sin6_addr;   //IPv6地址,网络字节顺序
};
struct in6_addr
{
    unsigned long   s6_addr;  //网络字节顺序的IPv6地址
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

IP地址转换函数

  • inet_aton():将字符串形式的IP地址转换成二进制形式的IP地址,成功返回1,否则返回0,转换后的IP地址存储在参数inp中。
  • inet_ntoa():将32位二进制形式的IP地址转换为数字点形式的IP地址,结果在函数返回值中返回。
unsigned long inet_aton(const char *cp, struct in_addr *inp);
char* inet_ntoa(struct in_addr in);
 
 
  • 1
  • 2
  • 1
  • 2

网络字节顺序

  • 字节序,顾名思义字节的顺序,就是大于一个字节的数据在内存中的存放顺序。
  • 在跨平台以及网络程序应用中字节序才是一个应该被考虑的问题。
  • 网络字节序是TCP/IP规定的一种数据表示格式,与具体的CPU类型、操作系统无关,从而可以保证数据在不同主机之间传输时能被正确解释。网络字节顺序采用big endian(大端字节序)。
  • Intel x86系列CPU使用的都是little endian(小端字节序)
  • 大端字节序(big-endian):低地址存放最高有效字节
  • 小端字节序(little-endian):低地址存放最低有效字节
  • 例如数字0x12345678(DWORD)在两种不同字节序CPU中的存储顺序如下所示:

字节顺序转换函数

  • 下面四个函数分别用于长整型和短整型数在网络字节序和主机字节序之间进行转换,其中s指short,l指long,h指host,n指network
#include <netinet/in.h>
unsigned long htonl(unsigned long host_long);
unsigned short htons(unsigned short host_short);
unsigned long ntohl(unsigned long net_long);
unsigned short ntohs(unsigned short net_short);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

什么时候要考虑字节序问题

  • 如果是应用层的数据,即对TCP/IP来说是透明的数据,不用考虑字节序的问题。因为接收端收到的顺序是和发送端一致的
  • 但对于TCP/IP的IP地址、端口号来说就不一样了,例如
unsigned short prot = 0x0012  //十进制18
struct sockaddr_in mysock;
mysock.sin_family = AF_INET;  //TCP地址结构
mysock.sin_port = prot;
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
  • 因为网络字节序是big endian,即低地址存放的是数值的高位,所以TCP/IP实际上把这个port解释为0x1200(十进制4608)。
  • 本来打算是要在端口18建立连接的,但TCP/IP协议栈却在端口4608建立了连接

套接字的工作原理

  • INET 套接字就是支持 Internet 地址族的套接字,它位于TCP协议之上,BSD套接字之下,
  • 如图所示,这里也体现了Linux网络模块分层的设计思想(图在PPT里,自己想象吧…)
  • INET和 BSD 套接字之间的接口通过 Internet 地址族套接字操作集实现,这些操作集实际是一组协议的操作例程,
  • 在include/linux/net.h中定义为proto_ops:
struct proto_ops {
    int family; 
    int (*release) (struct socket *sock);
    int (*bind) (struct socket *sock, struct sockaddr *umyaddr,   int sockaddr_len);
    int (*connect) (struct socket *sock, struct sockaddr *uservaddr, int sockaddr_len, int flags);
    int (*socketpair) (struct socket *sock1, struct socket *sock2);
    int (*accept) (struct socket *sock, struct socket *newsock, int flags);
    int (*getname) (struct socket *sock, struct sockaddr *uaddr, int *usockaddr_len, int peer);
    unsigned int (*poll) (struct file *file, struct socket *sock, struct poll_table_struct *wait);
    int (*ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg);
    int (*listen) (struct socket *sock, int len);
    int (*shutdown) (struct socket *sock, int flags);
    int (*setsockopt) (struct socket *sock, int level, int optname, char *optval, int optlen);
    int (*getsockopt) (struct socket *sock, int level, int optname, char *optval, int *optlen);
    int (*sendmsg) (struct socket *sock, struct msghdr *m, int  total_len, struct scm_cookie *scm);
    int (*recvmsg) (struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm);
    int (*mmap) (struct file *file, struct socket *sock, struct vm_area_struct * vma);
    ssize_t (*sendpage) (struct socket *sock, struct page *page, int offset, size_t size, int flags);
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 这个操作集类似于文件系统中的file_operations结构。BSD套接字层通过调用proto_ops 结构中的相应函数执行任务。
  • BSD套接字层向 INET 套接字层传递socket数据结构来代表一个BSD套接字,
socket 定义
  • socket结构在include/linux/net.h中定义:
struct socket {
   socket_state state;
   unsigned long flags;
   struct proto_ops *ops;
   struct inode *inode;
   struct fasync_struct *fasync_list; /* Asynchronous wake up list */
   struct file *file;       /* File back pointer for gc */
   struct sock *sk;
   wait_queue_head_t wait;
   short type;
   unsigned char passcred;
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 但在INET套接字层中,它利用自己的sock数据结构来代表该套接字,因此,这两个结构之间存在着链接关系
  • Sock结构定义于include/net/sock.h(此结构有80多行,在此不予列出
  • 在BSD的socket数据结构中存在一个指向sock的指针sk,而在sock中又有一个指向socket的指针,
  • 这两个指针将BSD socket数据结构和sock数据结构链接了起来。
  • 通过这种链接关系,套接字调用就可以方便地检索到sock数据结构
  • 实际上,sock数据结构可适用于不同的地址族,它也定义有自己的协议操作集proto
  • 进程在利用套接字进行通讯时,采用客户-服务器模型。服务器首先创建一个套接字,并将某个名称绑定到该套接字上,套接字的名称依赖于套接字的底层地址族,但通常是服务器的本地地址
/etc/services 文件
  • 对于INET套接字来说,服务器的地址由两部分组成:服务器的IP地址和服务器的端口地址。已注册的标准端口可查看/etc/services 文件
  • 将地址绑定到套接字之后,服务器就可以监听请求连接该绑定地址的传入连接
  • 连接请求由客户生成,它首先建立一个套接字,并指定服务器的目标地址以请求建立连接
  • 传入的连接请求通过不同的协议层到达服务器的监听套接字
  • 服务器接收到传入请求后,如果能够接受该请求,服务器必须创建一个新的套接字来接受该请求并建立通信连接(用于监听的套接字不能用来建立通信连接),这时,服务器和客户就可以利用建立好的通信连接传输数据
  • BSD套接字上的详细操作与具体的底层地址族有关,底层地址族的不同实际意味着寻址方式、采用的协议等的不同
  • Linux 利用BSD套接字层抽象了不同的套接字接口。在内核的初始化阶段,内建于内核的不同地址族分别以BSD套接字接口在内核中注册
  • 然后,随着应用程序创建并使用BSD套接字
  • 内核负责在BSD套接字和底层的地址族之间建立联系。这种联系通过交叉链接数据结构以及地址族专有的支持例程表建立
  • 在内核中,地址族和协议信息保存在inet_protos向量中,其定义于include/net/protocol.h
struct inet_protocol *inet_protos[MAX_INET_PROTOS];
/* This is used to register protocols. */
struct inet_protocol {
    int   (*handler)(struct sk_buff *skb);
    void  (*err_handler)(struct sk_buff *skb, u32 info);
    struct inet_protocol    *next;
    unsigned char protocol;
    unsigned char copy:1;
    void      *data;
    const char    *name;
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

建立套接字

  • Linux在利用socket()系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字类型以及协议,其函数定义于net/socket.c中
asmlinkage long sys_socket(int family, int type, int protocol) {
  int retval;
  struct socket *sock;
  retval = sock_create(family, type, protocol, &sock);
  if (retval < 0)
     goto out;
  retval = sock_map_fd(sock);
  if (retval < 0)
     goto out_release;
 out:
      /* It may be already another descriptor 8) Not kernel problem. */
     return retval;
 out_release:
     sock_release(sock);
     return retval;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
sockfs
  • 实际上,套接字对于用户程序而言就是特殊的已打开的文件。内核中为套接字定义了一种特殊的文件类型,形成一种特殊的文件系统sockfs
  • 所谓创建一个套接字,就是在sockfs文件系统中创建一个特殊文件,或者说一个节点,并建立起为实现套接字功能所需的一整套数据结构
  • 所以,函数sock_create()首先是建立一个socket数据结构,然后将其“映射”到一个已打开的文件中,进行socket结构和sock结构的分配和初始化
  • 实际上,socket结构与sock结构是同一事物的两个方面。如果说socket结构是面向进程和系统调用界面的,那么sock结构就是面向底层驱动程序的
  • 把与文件系统关系比较密切的那一部分放在socket结构中,把与通信关系比较密切的那一部分则单独组成一个数据结构,即sock结构
  • 由于这两部分数据在逻辑上本来就是一体的,所以要通过指针互相指向对方,形成一对一的关系

在INET BSD套接字上绑定(bind)地址

  • 为了监听传入的Internet 连接请求,每个服务器都需要建立一个INET BSD套接字,并且将自己的地址绑定到该套接字
  • 将地址绑定到某个套接字上之后,该套接字就不能用来进行任何其他的通信,因此,该socket数据结构的状态必须为TCP_CLOSE
  • 传递到绑定操作的sockaddr数据结构中包含要绑定的 IP地址以及一个可选的端口地址。被绑定的IP地址保存在sock数据结构的rcv_saddr和 saddr域中,这两个域分别用于哈希查找和发送用的IP地址。
  • 端口地址是可选的,如果没有指定,底层的支持网络会选择一个空闲的端口
  • 当底层网络设备接受到数据包时,它必须将数据包传递到正确的 INET 和 BSD 套接字以便进行处理,因此,TCP维护多个哈希表,用来查找传入 IP 消息的地址,并将它们定向到正确的socket/sock 对
  • TCP 并不在绑定过程中将绑定的sock数据结构添加到哈希表中,在这一过程中,它仅仅判断所请求的端口号当前是否正在使用。在监听操作中,该 sock 结构才被添加到 TCP 的哈希表中

在INET BSD套接字上建立连接 (connect)

  • 创建一个套接字之后,该套接字不仅可以用于监听入站的连接请求,也可以用于建立出站的连接请求。不论怎样都涉及到一个重要的过程:建立两个应用程序之间的虚拟电路。出站连接只能建立在处于正确状态的 INET BSD 套接字上,
  • 因此,不能建立于已建立连接的套接字,也不能建立于用于监听入站连接的套接字。也就是说,该 BSD socket 数据结构的状态必须为 SS_UNCONNECTED
  • 在建立连接过程中,双方 TCP 要进行三次“握手”。如果 TCP sock 正在等待传入消息,则该 sock 结构添加到 tcp_listening_hash 表中,这样,传入的 TCP 消息就可以定向到该 sock 数据结构

监听(listen) INET BSD 套接字

  • 当某个套接字被绑定了地址之后,该套接字就可以用来监听专属于该绑定地址的传入连接。网络应用程序也可以在未绑定地址之前监听套接字,这时,INET 套接字层将利用空闲的端口编号并自动绑定到该套接字。套接字的监听函数将 socket 的状态改变为TCP_LISTEN
  • 当接收到某个传入的 TCP 连接请求时,TCP 建立一个新的 sock 数据结构来描述该连接。当该连接最终被接受时,新的 sock 数据结构将变成该 TCP 连接的内核bottom_half部分,这时,它要克隆包含连接请求的传入 sk_buff 中的信息,并在监听 sock 数据结构的 receive_queue 队列中将克隆的信息排队。克隆的 sk_buff 中包含有指向新 sock 数据结构的指针

接受连接请求(accept)

  • 接受操作在监听套接字上进行,从监听 socket 中克隆一个新的 socket 数据结构。其过程如下:
  • 接受操作首先传递到支持协议层,即INET中,以便接受任何传入的连接请求。接受操作可以是阻塞的或是非阻塞的。非阻塞时,若没有可接受的传入连接,则接受操作将失败,而新建立的socket数据结构被抛弃。阻塞时,执行阻塞操作的网络应用程序将添加到等待队列中并保持挂起直到接收到一个TCP连接请求为止。
  • 当连接请求到达之后,包含连接请求的sk_buff被丢弃,而由TCP建立的新sock数据结构返回到INET套接字层,在这里,sock数据结构和先前建立的新socket数据结构建立链接。而新socket的文件描述符(fd)被返回到网络应用程序,此后,应用程序就可以利用该文件描述符在新建立的INET BSD套接字上进行套接字操作

套接字为用户提供的系统调用

  • 见PPT 3.7
getsockname()函数
  • getsockname(): 获取与当前套接字绑定的IP地址及端口
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 
 
  • 1
  • 2
  • 1
  • 2
  • 返回值:成功返回0,失败返回-1,并在errno中设置错误代码。
  • 错误代码:
EBADF :
The argument sockfd is not a valid descriptor.
EFAULT :
The addr argument points to memory not in a valid part of the process address space.
EINVAL :
addrlen is invalid (e.g., is negative).
ENOBUFS :
Insufficient resources were available in the system to perform the operation.
ENOTSOCK :
The argument sockfd is a file, not a socket.
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
getpeername()函数
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 
 
  • 1
  • 2
  • 1
  • 2
  • 返回值:成功返回0,失败返回-1,并在errno中设置错误代码。
  • 错误代码:
EBADF
The argument sockfd is not a valid descriptor.
EFAULT
The addr argument points to memory not in a valid part of the process address space.
EINVAL
addrlen is invalid (e.g., is negative).
ENOBUFS
Insufficient resources were available in the system to perform the operation.
ENOTCONN
The socket is not connected.
ENOTSOCK
The argument sockfd is a file, not a socket.
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
gethostbyname()和gethostbyaddr()
  • gethostbyname():主机名转换为IP地址
  • gethostbyaddr():IP地址转换成主机名
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
struct hostent {
       char    *h_name;           /* 主机的正式名称 */
       char    **h_aliases;       /* 主机别名列表 */
       int       h_addrtype;        /* 主机地址类型 */
       int       h_length;            /* 地址长度 */
       char    **h_addr_list;    /* 地址列表 */
};
#define     h_addr h_addr_list[0] /* 保持后向兼容 */
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
getservbyname()和getservbyport()
  • getservbyname():根据给定名字查找相应服务,返回服务的端口号
  • getservbyport():给定端口号和可选协议查找相应服务
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent {
    char *s_name; /* official service name */
    char **s_aliases; /* alias list */
    int s_port; /* port number */
    char *s_proto; /* protocol to use */
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
字节处理函数 bzero bcopy bcmp memset memcpy memcmp
  • 套接字地址是多字节数据,不是以空字符结尾的,Linux提供两组函数来处理多字节数据。一组函数以b开头,适合BSD系统兼容的函数;另一组函数以mem开头,是ANSI C提供的函数
#include<string.h>
void bzero(void *s,int n);
void bcopy(const void *src,void *dest,int n);
void bcmp(const void *s1,const void *s2,int n);
void *memset(void *s,int c,size_t n);
void *memcpy(void *dest,void *src,size_t n);
void memcmp(const void *s1, const void *s2,size_t n);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 函数bzero将参数s指定的内容的前n个字节设置为0,通常用它来将套接字地址清零
  • 函数bcopy从参数src指定的内存区域拷贝指定数目的字节内容到参数dest指定的内存区域
  • 函数bcmp比较参数s1指定的内存区域和参数s2指定的内存区域的前n个字节内容,相同则返回0,否则返回非0
  • 将参数s指定的内存区域的前n个字节设置为参数c的内容
  • 类似于bcopy,但bcopy能处理参数src和参数dest所指定的区域有重叠的情况,而memcpy不能
  • 比较参数s1和参数s2指定区域的前n个字节内容,相同则返回0,否则返回非0

小结

  • 套接字标识TCP/IP的连接
  • 使用套接字要注意:
  • 1、sockaddr与sockaddr_in的区别
  • 2、网络字节顺序
  • 了解套接字的工作原理
  • 掌握套接字的通信过程




给出在linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到客户端的连接后,发送数据给客户端;客户端在接受到数据后打印出来,然后关闭。程序里有详细的说明,其中对具体的结构体和函数的实现可以参考其他资料。

程序说明: 这里服务器的端口号和ip地址使用固定的设置,移植时可以根据具体情况更改,可以改写为参数传递更好,这里为了方便,使用固定的。

移植时服务端可以不用更改,编译后可直接运行;客户端将ip改为服务器的地址,然后编译运行。可以使用netstat 进行查看相应的运行状态。


[cpp]  view plain  copy
 print ?
  1. /************************************* 
  2. 文件名: server.c  
  3. linux 下socket网络编程简例  - 服务端程序 
  4. 服务器端口设为 0x8888   (端口和地址可根据实际情况更改,或者使用参数传入) 
  5. 服务器地址设为 192.168.1.104 
  6. 作者:kikilizhm#163.com (将#换为@) 
  7. */  
  8.   
  9. #include <stdlib.h>  
  10. #include <sys/types.h>  
  11. #include <stdio.h>  
  12. #include <sys/socket.h>  
  13. #include <linux/in.h>  
  14. #include <string.h>  
  15.   
  16. int main()  
  17. {  
  18. int sfp,nfp; /* 定义两个描述符 */  
  19. struct sockaddr_in s_add,c_add;  
  20. int sin_size;  
  21. unsigned short portnum=0x8888; /* 服务端使用端口 */  
  22.   
  23. printf("Hello,welcome to my server !\r\n");  
  24. sfp = socket(AF_INET, SOCK_STREAM, 0);  
  25. if(-1 == sfp)  
  26. {  
  27.     printf("socket fail ! \r\n");  
  28.     return -1;  
  29. }  
  30. printf("socket ok !\r\n");  
  31.   
  32. /* 填充服务器端口地址信息,以便下面使用此地址和端口监听 */  
  33. bzero(&s_add,sizeof(struct sockaddr_in));  
  34. s_add.sin_family=AF_INET;  
  35. s_add.sin_addr.s_addr=htonl(INADDR_ANY); /* 这里地址使用全0,即所有 */  
  36. s_add.sin_port=htons(portnum);  
  37. /* 使用bind进行绑定端口 */  
  38. if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))  
  39. {  
  40.     printf("bind fail !\r\n");  
  41.     return -1;  
  42. }  
  43. printf("bind ok !\r\n");  
  44. /* 开始监听相应的端口 */  
  45. if(-1 == listen(sfp,5))  
  46. {  
  47.     printf("listen fail !\r\n");  
  48.     return -1;  
  49. }  
  50. printf("listen ok\r\n");  
  51.   
  52. while(1)  
  53. {  
  54. sin_size = sizeof(struct sockaddr_in);  
  55. /* accept服务端使用函数,调用时即进入阻塞状态,等待用户进行连接,在没有客户端进行连接时,程序停止在此处, 
  56.    不会看到后面的打印,当有客户端进行连接时,程序马上执行一次,然后再次循环到此处继续等待。 
  57.    此处accept的第二个参数用于获取客户端的端口和地址信息。 
  58.     */  
  59. nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);  
  60. if(-1 == nfp)  
  61. {  
  62.     printf("accept fail !\r\n");  
  63.     return -1;  
  64. }  
  65. printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",ntohl(c_add.sin_addr.s_addr),ntohs(c_add.sin_port));  
  66.   
  67. /* 这里使用write向客户端发送信息,也可以尝试使用其他函数实现 */  
  68. if(-1 == write(nfp,"hello,welcome to my server \r\n",32))  
  69. {  
  70.     printf("write fail!\r\n");  
  71.     return -1;  
  72. }  
  73. printf("write ok!\r\n");  
  74. close(nfp);  
  75.   
  76. }  
  77. close(sfp);  
  78. return 0;  
  79. }  



[cpp]  view plain  copy
 print ?
  1. /************************************* 
  2. 文件名: client.c  
  3. linux 下socket网络编程简例  - 客户端程序 
  4. 服务器端口设为 0x8888   (端口和地址可根据实际情况更改,或者使用参数传入) 
  5. 服务器地址设为 192.168.1.104 
  6. 作者:kikilizhm#163.com (将#换为@) 
  7. */  
  8.   
  9. #include <stdlib.h>  
  10. #include <sys/types.h>  
  11. #include <stdio.h>  
  12. #include <sys/socket.h>  
  13. #include <linux/in.h>  
  14. #include <string.h>  
  15.   
  16. int main()  
  17. {  
  18. int cfd; /* 文件描述符 */  
  19. int recbytes;  
  20. int sin_size;  
  21. char buffer[1024]={0};    /* 接受缓冲区 */  
  22. struct sockaddr_in s_add,c_add; /* 存储服务端和本端的ip、端口等信息结构体 */  
  23. unsigned short portnum=0x8888;  /* 服务端使用的通信端口,可以更改,需和服务端相同 */  
  24.   
  25. printf("Hello,welcome to client !\r\n");  
  26. /* 建立socket 使用因特网,TCP流传输 */  
  27. cfd = socket(AF_INET, SOCK_STREAM, 0);  
  28. if(-1 == cfd)  
  29. {  
  30.     printf("socket fail ! \r\n");  
  31.     return -1;  
  32. }  
  33. printf("socket ok !\r\n");  
  34. /* 构造服务器端的ip和端口信息,具体结构体可以查资料 */  
  35. bzero(&s_add,sizeof(struct sockaddr_in));  
  36. s_add.sin_family=AF_INET;  
  37. s_add.sin_addr.s_addr= inet_addr("192.168.1.104"); /* ip转换为4字节整形,使用时需要根据服务端ip进行更改 */  
  38. s_add.sin_port=htons(portnum); /* 这里htons是将short型数据字节序由主机型转换为网络型,其实就是 
  39.     将2字节数据的前后两个字节倒换,和对应的ntohs效果、实质相同,只不过名字不同。htonl和ntohl是 
  40.     操作的4字节整形。将0x12345678变为0x78563412,名字不同,内容两两相同,一般情况下网络为大端, 
  41.     PPC的cpu为大端,x86的cpu为小端,arm的可以配置大小端,需要保证接收时字节序正确。 
  42.  */  
  43.   
  44. printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port); /* 这里打印出的是小端 
  45.     和我们平时看到的是相反的。 */  
  46.   
  47. /* 客户端连接服务器,参数依次为socket文件描述符,地址信息,地址结构大小 */  
  48. if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))  
  49. {  
  50.     printf("connect fail !\r\n");  
  51.     return -1;  
  52. }  
  53. printf("connect ok !\r\n");  
  54. /*连接成功,从服务端接收字符*/  
  55. if(-1 == (recbytes = read(cfd,buffer,1024)))  
  56. {  
  57.     printf("read data fail !\r\n");  
  58.     return -1;  
  59. }  
  60. printf("read ok\r\nREC:\r\n");  
  61.   
  62. buffer[recbytes]='\0';  
  63. printf("%s\r\n",buffer);  
  64.   
  65. getchar(); /* 此句为使程序暂停在此处,可以使用netstat查看当前的连接 */  
  66. close(cfd); /* 关闭连接,本次通信完成 */  
  67. return 0;  
  68.   
  69.   
  70.   
  71. }  


运行截图:






c语言网络编程-标准步骤,真的很简单啊

server.c

复制代码 代码如下:

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

#define PORT 4444
#define BACKLOG 5

int main(int argc, char *argv[]) {
int sock_fd, new_fd;
struct sockaddr_in server_addr, client_addr;
int sin_size;
int nbytes;
int on = 1;
char buffer[1024];

if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}

setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);

if (bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1) {
perror("bind");
exit(1);
}

if (listen(sock_fd, BACKLOG) == -1) {
perror("listen");
exit(1);
}

printf("Server start... \n");

while (1) {
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sock_fd, (struct sockaddr *)(&client_addr), &sin_size)) == -1) {
perror("accept");
exit(1);
}

printf("Server get connection from %s\n", inet_ntoa(client_addr.sin_addr));
if ((nbytes = read(new_fd, buffer, 1024)) == -1) {
perror("read");
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received: %s\n", buffer);
close(new_fd);
}

return 0;
}

client.c

复制代码 代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 4444

int main(int argc, char *argv[]) {
int sock_fd;
struct sockaddr_in server_addr;
struct hostent *host;
char buffer[1024];

if (argc < 2) {
perror("Need hostname");
exit(1);
}

if ((host = gethostbyname(argv[1])) == NULL) {
perror("gethostbyname");
exit(1);
}

if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}

memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);

if (connect(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1) {
perror("connect");
exit(1);
}

printf("Please input something:\n");
fgets(buffer, 1024, stdin);
write(sock_fd, buffer, strlen(buffer));
close(sock_fd);

return 0;
}




  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值