使用TCP协议的socket

1.网络字节序

由于在主机存储为小端序,网络传输为大端序,并且在网络中需要读取IP号和端口号,所以发送端要将小端序转为大端序,接收端将大端序转为小端序

#include <arpa/inet.h>
uint32_t htonl(uint32_t
hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t
netlong);
uint16_t ntohs(uint16_t netshort);

表示host,n表示network,l表示32位长整数,s表示16位短整数。

2.IP地址转换函数

#include <arpa/inet.h>
int inet_pton(int af, const
char *src, void *dst);
const char *inet_ntop(int af, const void *src, char
*dst, socklen_t size);

3.构造一个sockaddr

struct sockaddr_in servaddr;
bzero(&servaddr,
sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr
= htonl(INADDR_ANY);//本地任意IP(由于网卡可能绑定了多个IP)
servaddr.sin_port = htons(8000);

这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为8000。

4.socket函数

int socket(int domain, int type, int protocol);

参数:   domain:1.  AF_INET    (ipv4)   2.AF_INET6   (ipv6)

    type:      1. SOCK_STREAM (数据流)    2.SOCK_DGRAM(数据报)  3.SOCK_RAW(ICMP使用)

    protocol:  0 默认协议

返回值:成功返回一个新的文件描述符,失败返回-1,设置errno

5.绑定函数:bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

将构造的sockaddr和创建的socket绑定在一起。

参数:

  1.sockfd:socket文件描述符

  2.addr:构造出IP地址加端口号

  3.addrlen:sizeof(addr)长度

返回值:成功返回0,失败返回-1, 设置errno

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。

6.监听函数:listen

int listen(int sockfd, int backlog);

参数:1.sockfd:socket文件描述符

     2.backlog:排队建立3次握手队列和刚刚建立3次握手队列的链接数和

返回值:listen()成功返回0,失败返回-1。

7.accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:   1.sockdf:socket文件描述符
    2.addr:传出参数,返回链接客户端地址信息,含IP地址和端口号
    3.addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

三方握手完成后,服务器调用accept()接受连接

accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的参数。accept()成功返回一个文件描述符,出错返回-1。

8.connect

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockdf:socket文件描述符
addr:传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:传入参数,传入sizeof(addr)大小
返回值:成功返回0,失败返回-1,设置errno
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。

C/S模型-TCP

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

数据传输的过程:建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客11.3节C/S模型-TCP 123
户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一
条请求,如此循环下去。如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。

注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

可以看出在TCP建立连接后发送数据:可以用send和recv,也可以用read和write。

实例:server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
//创建socket
listenfd = socket(AF_INET, SOCK_STREAM, );
//设置sockaddr
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//将socket和sockaddr绑定
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//监听
listen(listenfd, );
printf("Accepting connections ...\n");
while ()
{
cliaddr_len = sizeof(cliaddr);//每次都要更新
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = ; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
}

server.c

client.c的作用是从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的

/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc != )
{
fputs("usage: ./client message\n", stderr);
exit();
}
str = argv[];
//建立socket
sockfd = socket(AF_INET, SOCK_STREAM, );
//设置sockaddr
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);//将十进制ip地址转化
servaddr.sin_port = htons(SERV_PORT);
//发起连接
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//向服务器发送数据
write(sockfd, str, strlen(str));
//收数据
n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
//打印到输出设备
write(STDOUT_FILENO, buf, n);
//关闭套接字
close(sockfd);
return ;
}

client.c

注意:recv和send函数  与  read和write函数类似,只多了一个参数flag 一般默认为0.

由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。

C/S模型-UDP

服务器端:

/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int sockfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
//创建socket
sockfd = Socket(AF_INET, SOCK_DGRAM, );
//设置sockaddr
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//将socket与sockaddr绑定
Bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Accepting connections ...\n");
while ()
{
cliaddr_len = sizeof(cliaddr);
//接收来自客户端的数据
n = recvfrom(sockfd, buf, MAXLINE, , (struct sockaddr *)&cliaddr, &cliaddr_len);
if (n == -)
perr_exit("recvfrom error");
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = ; i < n; i++)
buf[i] = toupper(buf[i]);
//向服务器端发送数据
n = sendto(sockfd, buf, n, , (struct sockaddr *)&cliaddr, sizeof(cliaddr));
if (n == -)
perr_exit("sendto error");
}
}

server.c

客户端:

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
int sockfd, n;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t servaddr_len;
//创建socket
sockfd = Socket(AF_INET, SOCK_DGRAM, );
//设置sockaddr
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
while (fgets(buf, MAXLINE, stdin) != NULL)
{
//向服务器端发送数据
n = sendto(sockfd, buf, strlen(buf), , (struct sockaddr *)&servaddr, sizeof(servaddr));
if (n == -)
perr_exit("sendto error");
//从客户端接收数据
n = recvfrom(sockfd, buf, MAXLINE, , NULL, );
if (n == -)
perr_exit("recvfrom error");
//显示到屏幕上
Write(STDOUT_FILENO, buf, n);
}
//关闭套接字
Close(sockfd);
return ;
}

client.c

recvfrom和sendto   的第五个参数为传出参数,记录发来的数据源sockaddr,服务器根据这个参数记录客户端的socket信息,并发送数据。

Linux网络通信的更多相关文章

  1. Linux网络通信(TCP套接字编写,多进程多线程版本)

    预备知识 源IP地址和目的IP地址 IP地址在上一篇博客中也介绍过,它是用来标识网络中不同主机的地址.两台主机进行通信时,发送方需要知道自己往哪一台主机发送,这就需要知道接受方主机的的IP地址,也就是 ...

  2. Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)

    Linux下测试代码: http://www.linuxhowtos.org/C_C++/socket.htm TCP模型 //TCPClient.c #include<string.h> ...

  3. Linux 网络通信命令之 netstat

    定义 Linux netstat命令用于显示网络状态. 利用netstat指令可让你得知整个Linux系统的网络情况. 语法 netstat [-acCeFghilMnNoprstuvVwx][-A& ...

  4. (54)LINUX应用编程和网络编程之九Linux网络通信实践

    3.9.1.linux网络编程框架 3.9.1.1.网络是分层的 (1)OSI 7层模型(理论指导) (2)网络为什么要分层 (3)网络分层的具体表现 3.9.1.2.TCP/IP协议引入(网络分层实 ...

  5. linux 网络通信

    网络命令 历史上最早的即时通信 1 write linzhiling (内容) ctrl+D结束 这样一封信就写出去了,注意:只有用户在线才可以发送 2 wall (write all) 给所有的用户 ...

  6. Linux 网络通信 API详解【转载】

    TCP/IP分层模型 OSI协议参考模型,它是基于国际标准化组织(ISO)的建议发展起来的, 它分为7个层次:应用层.表示层.会话层.传输层.网络层.数据链路层及物理层. 这个7层的协议模型虽然规定得 ...

  7. Linux网络通信(线程池和线程池版本的服务器代码)

    线程池 介绍 线程池: 一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的 ...

  8. 【linux草鞋应用编程系列】_5_ Linux网络编程

    一.网络通信简介   第一部分内容,暂时没法描述,内容实在太多,待后续专门的系列文章.   二.linux网络通信     在linux中继承了Unix下“一切皆文件”的思想, 在linux中要实现网 ...

  9. 【转】linux驱动开发的经典书籍

    原文网址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书 ...

随机推荐

  1. ASM配置管理

    http://blog.chinaunix.net/uid-22646981-id-3060280.htmlhttp://blog.sina.com.cn/s/blog_6a5aa0300102uys ...

  2. sql表设计

    数据库实际上是系统逻辑在磁盘上的固化,是信息河流的蓄水池. 数据库的表应有如下类型 1)类表.配置表.作为业务逻辑基本的名字,状态的定义,作为构建逻辑世界的最基础框架,解释框架的框架. 特点,数据不会 ...

  3. 好强大的vim配置文件

    原文链接 http://www.cnblogs.com/ma6174/archive/2011/12/10/2283393.html

  4. red hat官方的rhel操作系统版本号与内核版本号的对应关系

    原文在如下网址:https://access.redhat.com/articles/3078 The tables below list the major and minor Red Hat En ...

  5. 1041 Be Unique (20 分)

    1041 Be Unique (20 分) Being unique is so important to people on Mars that even their lottery is desi ...

  6. 报表系统OLAP

    实现报表系统可自定义定制: 1.纬度,事实表,用户自定义观察纬度,实现报表自定义(自定义纬度树结构---通过这个树结构,后台自动生成报表SQL,用户仅仅配置纬度关系即可)----报表配置纬度关 系 2 ...

  7. C#中 ThreadStart和ParameterizedThreadStart区别

    最主要区别: 1.Thread 是启动一个线程,但是没有参数. 2.ParameterThreadStart  线程可以接受一个输入参数 ThreadStart: ThreadStart这个委托定义为 ...

  8. BigDecimal空指针异常——个人应用

    背景: 将数据库统计的数据,封装成了两个BigDecimal,此时要将两个BigDecimal进行运算.其中有一个没有数据的话,会报null(不管null值在前在后) 先上解决: 我把数据库的数据进行 ...

  9. spring boot + jpa + kotlin入门实例

    spring boot +jpa的文章网络上已经有不少,这里主要补充一下用kotlin来做. kotlin里面的data class来创建entity可以帮助我们减少不少的代码,比如现在这个User的 ...

  10. msq_table's methods

    -- 查看有哪些用户 host: % 代表任意地址都可以登录 host: localhost 代表仅本地可以连接select host,user from mysql.user; -- 建库 crea ...