一.IP和端口
IP地址是一个规定,现在使用的是IPv4,既由4个0-255之间的数字组成,在计算机内部存储时只需要4个字节即可。在计算机中,IP地址是分配给网卡的,每个网卡有一个唯一的IP地址,如果一个计算机有多个网卡,则该台计算机则拥有多个不同的IP地址,在同一个网络内部,IP地址不能相同。IP地址的概念类似于电话号码、身份证这样的概念。由于IP地址不方便记忆,所以有专门创造了域名(Domain Name)的概念,其实就是给IP取一个字符的名字,例如163.com、sina.com等。IP和域名之间存在一定的对应关系。如果把IP地址类比成身份证号的话,那么域名就是你的姓名。
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。
其实在网络中只能使用IP地址进行数据传输,所以在传输以前,需要把域名转换为IP,这个由称作DNS的服务器专门来完成。 所以在网络编程中,可以使用IP或域名来标识网络上的一台设备。 为了在一台设备上可以运行多个程序,人为的设计了端口(Port)的概念,类似的例子是公司内部的分机号码。规定一个设备有216个,也就是65536个端口,每个端口对应一个唯一的程序。每个网络程序,无论是客户端还是服务器端,都对应一个或多个特定的端口号。由于0-1024之间多被操作系统占用,所以实际编程时一般采用1024以后的端口号。 下面是一些常见的服务对应的端口:
ftp:23,telnet:23,smtp:25,dns:53,http:80,https:443
使用端口号,可以找到一台设备上唯一的一个程序。 所以如果需要和某台计算机建立连接的话,只需要知道IP地址或域名即可,但是如果想和该台计算机上的某个程序交换数据的话,还必须知道该程序使用的端口号。
我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
socket
我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
http://www.cnblogs.com/dolphinX/p/3460545.html 讲得不错,对理解下面两个程序有帮助
1.时间获取客户程序(TCP)
tips:
①int main(int argc, char **argv)
argc是参数个数,二维字符串数组argv是命令行参数;
② sockaddr_in 地址结构体。元素如下:
sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序),在linux下,端口号的范围0~65535,同时0~1024范围的端口号已经被系统使用或保留。
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
s_addr按照网络字节顺序存储IP地址
③socket函数:int socket(int domain, int type, int protocol)
函数返回一个整型的socket描述符(sockfd:socket file descriptor文件描述符),供后面的使用,其中参数:
1. domain:指明使用的协议族,值:
AF_INET:用于网络通信
AF_UNIX:单一Unix系统中进程间通信
2.type: 指明socket类型,值
SOCK_STREAM:流式,面向连接的比特流,顺序、可靠、双向,用于TCP通信
SOCK_DGRAM: 数据报式,无连接的,定长、不可靠 UDP通信
3.protocol:由于指定了type,这里一般用“0”
一个socket的建立:int fd = socket(AF_INET, SOCK_STREAM, 0)
④inet_pton函数:int inet_pton(int af, const char *src, void *dst)
这个函数转换字符串到网络地址。
第一个参数af是地址簇;
第二个参数*src是来源地址;
第三个参数* dst接收转换后的数据。
⑤int connect (int sockfd, struct sockaddr * serv_addr, int addrlen);
用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址。
参数一:套接字描述符
参数二:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
参数三:参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得
⑥ssize_t read(int fd,void * buf ,size_t count);
read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。
代码:
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: a.out <IPaddress>");
/*
创建TCP套接字
*/
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
/*
指定服务器的ip和端口
*/
bzero(&servaddr, sizeof(servaddr)); //把结构体变量清零
servaddr.sin_family = AF_INET;//TCP协议簇
servaddr.sin_port = htons(13);//htons函数,把端口号转换成二进制
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) //IP地址是第一个命令行参数的值:arvg[1]
err_quit("inet_pton error for %s", argv[1]);
/*
建立和服务器的连接
*/
//调用connect发起主动打开,导致客户tcp发送一个同步分节
if (connect(sockfd, (SA *)&servaddr, sizeof(servaddr)) < 0) //三次握手,建立连接。其中sockfd就是socket的文件描述符。
err_sys("connect error");
/*
读入并输出服务器的应答
*/
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; // null terminate ,结束终止符
if (fputs(recvline, stdout) == EOF) //fputs就是I/O函数,fputs向指定文件写入字符串。参数:字符串,文件指针,遇到0终止输出。
err_sys("fputs error");
}
if (n < 0)
err_sys("read error");
exit(0);
}
2.时间获取服务器程序(TCP)
代码:
#include "unp.h"
#include <time.h>
int
main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
//准备监听描述符的三个步骤:socket,bind,listen.通过调用这三个函数完成被动打开(准备好接受外来连接)
listenfd = Socket(AF_INET, SOCK_STREAM, 0);//AF_INET:采用TCP/IP协议簇;SOCK_STREAM:采用TCP协议,0是默认情况
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY是inet_addr("0.0.0.0"),如果服务器主机有多个网络接口,服务器进程就可以在任意网络接口上受客户连接
servaddr.sin_port = htons(13);//socket对应的端口(找到ip就是找到主机,再找到端口才是确定进程) 时间服务器接口都是13
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程)
Listen(listenfd, LISTENQ); // 将socket设置为监听模式,5表示等待连接队列的最大长度(排队的最大客户连接数)
// listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
//listen函数一般在调用bind之后-调用accept之前调用。
for ( ; ; ) {
connfd = Accept(listenfd, (SA *) NULL, NULL);//accept:在一个套接口接受一个连接。如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));//在字符串末尾添加
Write(connfd, buff, strlen(buff));// 参数1是文件描述符;参数2是指定的缓冲区,即指针,指向一段内存单元;参数3是要写入文件指定的字节数;返回值:写入文档的字节数(成功);-1(出错)
//write函数把buff中nbyte写入文件描述符connfd所指的文档,成功时返回写的字节数,错误时返回-1.
Close(connfd);//终止连接
}
}
最后结果:
client:
#include <stdio.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include<stdlib.h>
#include <netinet/in.h>
#define SA struct sockaddr
#define MAXLINE 1000
int main(int argc,char **argv)
{
// printf("23333\n");
int sockfd,n;
char recvline[MAXLINE+1];
struct sockaddr_in servaddr;
if (argc!=2){
printf("IP\n");
exit(0);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("socket error\n");
exit(0);
}
// printf(" 333\n");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6789);
if((inet_pton(AF_INET, argv[1], &servaddr.sin_addr)) < 0){
printf("inet_pton error for %s\n",argv[1]);
exit(0);
}
int xx = connect(sockfd, (SA *)&servaddr,sizeof(servaddr));
if(xx < 0){
printf("connect error\n");
exit(0);
}
printf("已建立连接\n");
while((n = read(sockfd, recvline, MAXLINE)) > 0){
recvline[n] = '\0';
printf(" 33\n");
if(fputs(recvline, stdout) == EOF){
printf("fputs error\n");
exit(0);
}
}
if(n < 0){
printf("read error\n");
exit(0);
}
exit(0);
}
server
#include <stdio.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#define SA struct sockaddr
#define LISTENQ 10
#define MAXLINE 1000
int main(int argc, char **argv){
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6789);
bind(listenfd, (SA *) &servaddr,sizeof(servaddr));
listen(listenfd, LISTENQ);
for( ; ;){
connfd = accept(listenfd, (SA *) NULL, NULL);
snprintf(buff, sizeof(buff), "%s\r\n","get it!");
write(connfd, buff, strlen(buff));
close(connfd);
}
}
tip:从vim复制代码:cat + xx.c
再复制就好啦!